init commit
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package `in`.sminnovations.smiadmin
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
|
||||
class EmployeeListAdapter(
|
||||
private val employees: MutableList<Employee> = mutableListOf(),
|
||||
private val onStatusChanged: (Employee, Boolean) -> Unit
|
||||
) : RecyclerView.Adapter<EmployeeListAdapter.EmployeeViewHolder>() {
|
||||
|
||||
class EmployeeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val nameTextView: TextView = itemView.findViewById(R.id.textViewEmployeeName)
|
||||
val statusSwitch: SwitchMaterial = itemView.findViewById(R.id.switchStatus)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeeViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_employee, parent, false)
|
||||
return EmployeeViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: EmployeeViewHolder, position: Int) {
|
||||
val employee = employees[position]
|
||||
|
||||
// Remove the previous listener to avoid callbacks while setting up the view
|
||||
holder.statusSwitch.setOnCheckedChangeListener(null)
|
||||
|
||||
// Set the text and switch state
|
||||
holder.nameTextView.text = employee.Name
|
||||
holder.statusSwitch.isChecked = employee.EmployeeStatus
|
||||
|
||||
// Set up the new listener after setting the checked state
|
||||
holder.statusSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
if (buttonView.isPressed) { // Only trigger if user actually clicked
|
||||
employee.EmployeeStatus = isChecked // Update local data
|
||||
onStatusChanged(employee, isChecked) // Update Firestore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = employees.size
|
||||
|
||||
fun updateEmployees(newEmployees: List<Employee>) {
|
||||
employees.clear()
|
||||
employees.addAll(newEmployees)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package `in`.sminnovations.smiadmin.data
|
||||
|
||||
data class AttendanceData(
|
||||
val date: String = "",
|
||||
val checkIn: String = "",
|
||||
val checkOut: String = ""
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package `in`.sminnovations.smiadmin.data
|
||||
|
||||
data class Employee(
|
||||
val Id: String = "",
|
||||
val Name: String = "",
|
||||
var EmployeeStatus: Boolean = false
|
||||
)
|
||||
177
app/src/main/java/in/sminnovations/smiadmin/ui/CSVGenerator.kt
Normal file
177
app/src/main/java/in/sminnovations/smiadmin/ui/CSVGenerator.kt
Normal file
@@ -0,0 +1,177 @@
|
||||
package `in`.sminnovations.smiadmin.ui
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.firestore.firestore
|
||||
import `in`.sminnovations.smiadmin.data.AttendanceData
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class CSVGenerator(private val context: Context) {
|
||||
suspend fun generateAttendanceReport(
|
||||
employees: List<Employee>,
|
||||
selectedYear: Int,
|
||||
selectedMonth: Int
|
||||
): File {
|
||||
val fileName = "Attendance_${selectedYear}_${selectedMonth}.csv"
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "text/csv")
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
|
||||
val uri = context.contentResolver.insert(
|
||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
) ?: throw IOException("Failed to create new MediaStore record.")
|
||||
|
||||
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
val writer = outputStream.bufferedWriter()
|
||||
|
||||
val dates = mutableSetOf<String>()
|
||||
val attendanceData = mutableMapOf<String, Map<String, AttendanceData>>()
|
||||
|
||||
// Format month with leading zero
|
||||
val monthStr = String.format("%02d", selectedMonth)
|
||||
|
||||
// Collect attendance data for each employee
|
||||
employees.forEach { employee ->
|
||||
// Get all documents and filter by month pattern in document ID
|
||||
val snapshot = Firebase.firestore
|
||||
.collection("attendance_${employee.Id}")
|
||||
.get()
|
||||
.await()
|
||||
|
||||
val employeeAttendance = snapshot.documents
|
||||
.filter { doc ->
|
||||
// Filter documents that match the pattern DD-MM-YYYY for selected month
|
||||
doc.id.matches(Regex("\\d{2}-$monthStr-$selectedYear"))
|
||||
}
|
||||
.associate { doc ->
|
||||
dates.add(doc.id)
|
||||
doc.id to AttendanceData(
|
||||
date = doc.id,
|
||||
checkIn = doc.getString("checkIn") ?: "N/A",
|
||||
checkOut = doc.getString("checkOut") ?: "N/A"
|
||||
)
|
||||
}
|
||||
attendanceData[employee.Id] = employeeAttendance
|
||||
}
|
||||
|
||||
// Sort dates
|
||||
val sortedDates = dates.sortedBy { date ->
|
||||
val parts = date.split("-")
|
||||
val day = parts[0].toInt()
|
||||
String.format("%02d", day)
|
||||
}
|
||||
|
||||
// Write header
|
||||
val headerRow = listOf("Employee ID", "Name") + sortedDates
|
||||
writer.write(headerRow.joinToString(","))
|
||||
writer.newLine()
|
||||
|
||||
// Write employee data
|
||||
employees.forEach { employee ->
|
||||
val rowData = mutableListOf<String>()
|
||||
rowData.add(employee.Id)
|
||||
rowData.add(employee.Name)
|
||||
|
||||
val employeeAttendance = attendanceData[employee.Id] ?: emptyMap()
|
||||
sortedDates.forEach { date ->
|
||||
val attendance = employeeAttendance[date]
|
||||
val cellValue = when {
|
||||
attendance == null -> "Absent"
|
||||
attendance.checkOut == "N/A" -> attendance.checkIn
|
||||
else -> "${attendance.checkIn} - ${attendance.checkOut}"
|
||||
}
|
||||
rowData.add("\"${cellValue.replace("\"", "\"\"")}\"")
|
||||
}
|
||||
|
||||
writer.write(rowData.joinToString(","))
|
||||
writer.newLine()
|
||||
}
|
||||
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
File(Environment.getExternalStorageDirectory(), "Download/$fileName")
|
||||
} else {
|
||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val file = File(downloadsDir, fileName)
|
||||
|
||||
file.bufferedWriter().use { writer ->
|
||||
val dates = mutableSetOf<String>()
|
||||
val attendanceData = mutableMapOf<String, Map<String, AttendanceData>>()
|
||||
|
||||
val monthStr = String.format("%02d", selectedMonth)
|
||||
|
||||
employees.forEach { employee ->
|
||||
val snapshot = Firebase.firestore
|
||||
.collection("attendance_${employee.Id}")
|
||||
.get()
|
||||
.await()
|
||||
|
||||
val employeeAttendance = snapshot.documents
|
||||
.filter { doc ->
|
||||
doc.id.matches(Regex("\\d{2}-$monthStr-$selectedYear"))
|
||||
}
|
||||
.associate { doc ->
|
||||
dates.add(doc.id)
|
||||
doc.id to AttendanceData(
|
||||
date = doc.id,
|
||||
checkIn = doc.getString("checkIn") ?: "N/A",
|
||||
checkOut = doc.getString("checkOut") ?: "N/A"
|
||||
)
|
||||
}
|
||||
attendanceData[employee.Id] = employeeAttendance
|
||||
}
|
||||
|
||||
val sortedDates = dates.sortedBy { date ->
|
||||
val parts = date.split("-")
|
||||
val day = parts[0].toInt()
|
||||
String.format("%02d", day)
|
||||
}
|
||||
|
||||
val headerRow = listOf("Employee ID", "Name") + sortedDates
|
||||
writer.write(headerRow.joinToString(","))
|
||||
writer.newLine()
|
||||
|
||||
employees.forEach { employee ->
|
||||
val rowData = mutableListOf<String>()
|
||||
rowData.add(employee.Id)
|
||||
rowData.add(employee.Name)
|
||||
|
||||
val employeeAttendance = attendanceData[employee.Id] ?: emptyMap()
|
||||
sortedDates.forEach { date ->
|
||||
val attendance = employeeAttendance[date]
|
||||
val cellValue = when {
|
||||
attendance == null -> "Absent"
|
||||
attendance.checkOut == "N/A" -> attendance.checkIn
|
||||
else -> "${attendance.checkIn} - ${attendance.checkOut}"
|
||||
}
|
||||
rowData.add("\"${cellValue.replace("\"", "\"\"")}\"")
|
||||
}
|
||||
|
||||
writer.write(rowData.joinToString(","))
|
||||
writer.newLine()
|
||||
}
|
||||
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package `in`.sminnovations.smiadmin.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import `in`.sminnovations.smiadmin.EmployeeListAdapter
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
import `in`.sminnovations.smiadmin.databinding.FragmentEmployeeListBinding
|
||||
|
||||
|
||||
class EmployeeListFragment : Fragment() {
|
||||
private var _binding: FragmentEmployeeListBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var adapter: EmployeeListAdapter
|
||||
private val db = FirebaseFirestore.getInstance()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentEmployeeListBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupRecyclerView()
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
adapter = EmployeeListAdapter { employee, newStatus ->
|
||||
updateEmployeeStatus(employee, newStatus)
|
||||
}
|
||||
binding.recyclerViewEmployees.adapter = adapter
|
||||
}
|
||||
|
||||
private fun fetchEmployees() {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
db.collection("employees")
|
||||
.addSnapshotListener { snapshot, error ->
|
||||
binding.progressBar.visibility = View.GONE
|
||||
|
||||
if (error != null) {
|
||||
// Handle error
|
||||
Toast.makeText(context, "Error: ${error.message}", Toast.LENGTH_SHORT).show()
|
||||
return@addSnapshotListener
|
||||
}
|
||||
|
||||
snapshot?.let {
|
||||
val employeeList = snapshot.documents.map { doc ->
|
||||
Employee(
|
||||
Id = doc.getString("Id") ?: "",
|
||||
Name = doc.getString("Name") ?: "",
|
||||
EmployeeStatus = doc.getBoolean("EmployeeStatus") ?: false
|
||||
)
|
||||
}
|
||||
adapter.updateEmployees(employeeList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEmployeeStatus(employee: Employee, newStatus: Boolean) {
|
||||
val wasEnabled = employee.EmployeeStatus
|
||||
|
||||
// Optimistically update the UI
|
||||
employee.EmployeeStatus = newStatus
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
db.collection("employees")
|
||||
.document(employee.Id)
|
||||
.update("EmployeeStatus", newStatus)
|
||||
.addOnSuccessListener {
|
||||
// Update was successful
|
||||
Toast.makeText(context, "Status updated successfully", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
// Revert the change on failure
|
||||
employee.EmployeeStatus = wasEnabled
|
||||
adapter.notifyDataSetChanged()
|
||||
Toast.makeText(context, "Error updating status: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
170
app/src/main/java/in/sminnovations/smiadmin/ui/HomeFragment.kt
Normal file
170
app/src/main/java/in/sminnovations/smiadmin/ui/HomeFragment.kt
Normal file
@@ -0,0 +1,170 @@
|
||||
package `in`.sminnovations.smiadmin.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.firestore.firestore
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HomeFragment : Fragment() {
|
||||
private val db = Firebase.firestore
|
||||
private val STORAGE_PERMISSION_CODE = 101
|
||||
private var currentExcelTask: Job? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
view.findViewById<CardView>(R.id.cardEmployeeStatus).setOnClickListener {
|
||||
findNavController().navigate(R.id.action_home_to_employee_list)
|
||||
}
|
||||
|
||||
view.findViewById<CardView>(R.id.cardAttendanceReport).setOnClickListener {
|
||||
findNavController().navigate(R.id.action_home_to_employee_attendance_list)
|
||||
}
|
||||
|
||||
view.findViewById<CardView>(R.id.cardDownloadReport).setOnClickListener {
|
||||
if (checkStoragePermission()) {
|
||||
showMonthYearPicker()
|
||||
} else {
|
||||
requestStoragePermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMonthYearPicker() {
|
||||
MonthYearPickerDialog().apply {
|
||||
setOnDateSelectedListener { year, month ->
|
||||
downloadAttendanceReport(year, month)
|
||||
}
|
||||
}.show(parentFragmentManager, "MonthYearPicker")
|
||||
}
|
||||
|
||||
private fun downloadAttendanceReport(year: Int, month: Int) {
|
||||
currentExcelTask?.cancel()
|
||||
|
||||
val progressDialog = ProgressDialog(requireContext()).apply {
|
||||
setMessage("Generating report...")
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
|
||||
currentExcelTask = lifecycleScope.launch {
|
||||
try {
|
||||
// First get all employees
|
||||
val employeesSnapshot = withContext(Dispatchers.IO) {
|
||||
db.collection("employees").get().await()
|
||||
}
|
||||
|
||||
val employees = employeesSnapshot.documents.map { doc ->
|
||||
Employee(
|
||||
Id = doc.getString("Id") ?: "Unknown",
|
||||
Name = doc.getString("Name") ?: "Unknown"
|
||||
)
|
||||
}.sortedBy { it.Name }
|
||||
|
||||
// Generate CSV file
|
||||
val csvGenerator = CSVGenerator(requireContext())
|
||||
val csvFile = csvGenerator.generateAttendanceReport(employees, year, month)
|
||||
|
||||
progressDialog.dismiss()
|
||||
|
||||
// Show success message with file location
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Report saved in Downloads folder: ${csvFile.name}",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
} catch (e: Exception) {
|
||||
progressDialog.dismiss()
|
||||
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStoragePermission(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
Environment.isExternalStorageManager()
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
STORAGE_PERMISSION_CODE
|
||||
)
|
||||
} else {
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", requireContext().packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
if (requestCode == STORAGE_PERMISSION_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Permission granted, now show the picker
|
||||
showMonthYearPicker()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Storage permission is required to download reports",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package `in`.sminnovations.smiadmin.ui
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
import java.util.Calendar
|
||||
|
||||
class MonthYearPickerDialog : DialogFragment() {
|
||||
private var onDateSelected: ((Int, Int) -> Unit)? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val calendar = Calendar.getInstance()
|
||||
val year = calendar.get(Calendar.YEAR)
|
||||
val month = calendar.get(Calendar.MONTH)
|
||||
|
||||
val dialog = DatePickerDialog(
|
||||
requireContext(),
|
||||
R.style.DialogTheme,
|
||||
{ _, selectedYear, selectedMonth, _ ->
|
||||
onDateSelected?.invoke(selectedYear, selectedMonth + 1)
|
||||
},
|
||||
year,
|
||||
month,
|
||||
1
|
||||
)
|
||||
|
||||
// Hide the day picker as we only need month and year
|
||||
val datePicker = dialog.datePicker
|
||||
datePicker.findViewById<View>(Resources.getSystem().getIdentifier("day", "id", "android"))?.visibility = View.GONE
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
fun setOnDateSelectedListener(listener: (Int, Int) -> Unit) {
|
||||
onDateSelected = listener
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package `in`.sminnovations.smiadmin.ui
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val root = inflater.inflate(R.layout.fragment_settings, container, false)
|
||||
|
||||
// Set app version
|
||||
val versionTextView: TextView = root.findViewById(R.id.textViewVersion)
|
||||
try {
|
||||
val pInfo = requireContext().packageManager.getPackageInfo(
|
||||
requireContext().packageName, 0
|
||||
)
|
||||
val version = pInfo.versionName
|
||||
val verCode = pInfo.versionCode
|
||||
versionTextView.text = version + " (" + verCode +")"
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
|
||||
return root
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package `in`.sminnovations.smiadmin.ui.attendance
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
import `in`.sminnovations.smiadmin.data.AttendanceData
|
||||
|
||||
class AttendanceAdapter : RecyclerView.Adapter<AttendanceAdapter.ViewHolder>() {
|
||||
private val attendanceList = mutableListOf<AttendanceData>()
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val dateTextView: TextView = itemView.findViewById(R.id.textViewDate)
|
||||
val checkInTextView: TextView = itemView.findViewById(R.id.textViewCheckIn)
|
||||
val checkOutTextView: TextView = itemView.findViewById(R.id.textViewCheckOut)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_attendance, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val attendance = attendanceList[position]
|
||||
holder.dateTextView.text = attendance.date
|
||||
holder.checkInTextView.text = "Check In: ${attendance.checkIn}"
|
||||
holder.checkOutTextView.text = "Check Out: ${attendance.checkOut}"
|
||||
}
|
||||
|
||||
override fun getItemCount() = attendanceList.size
|
||||
|
||||
// In AttendanceAdapter.kt
|
||||
fun updateAttendance(newAttendance: List<AttendanceData>) {
|
||||
attendanceList.clear()
|
||||
// Convert date string to comparable format and sort
|
||||
attendanceList.addAll(newAttendance.sortedByDescending {
|
||||
// Convert dd-MM-yyyy to comparable format
|
||||
val parts = it.date.split("-")
|
||||
val day = parts[0].toInt()
|
||||
val month = parts[1].toInt()
|
||||
val year = parts[2].toInt()
|
||||
String.format("%04d%02d%02d", year, month, day)
|
||||
})
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package `in`.sminnovations.smiadmin.ui.attendance
|
||||
|
||||
import android.R
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import `in`.sminnovations.smiadmin.data.AttendanceData
|
||||
import `in`.sminnovations.smiadmin.databinding.FragmentAttendanceDetailBinding
|
||||
import java.util.Calendar
|
||||
|
||||
class AttendanceDetailFragment : Fragment() {
|
||||
private var _binding: FragmentAttendanceDetailBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var adapter: AttendanceAdapter
|
||||
private val db = FirebaseFirestore.getInstance()
|
||||
private val months = arrayOf("January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December")
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentAttendanceDetailBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val employeeId = arguments?.getString("employeeId") ?: return
|
||||
val employeeName = arguments?.getString("employeeName") ?: "Unknown"
|
||||
|
||||
binding.textViewEmployeeName.text = employeeName
|
||||
setupMonthSpinner()
|
||||
setupRecyclerView()
|
||||
|
||||
// Get current month (1-12)
|
||||
val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1
|
||||
fetchAttendanceData(employeeId, currentMonth)
|
||||
}
|
||||
|
||||
private fun setupMonthSpinner() {
|
||||
val adapter = ArrayAdapter(
|
||||
requireContext(),
|
||||
R.layout.simple_spinner_item,
|
||||
months
|
||||
)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
|
||||
binding.spinnerMonth.adapter = adapter
|
||||
// Set current month as default
|
||||
binding.spinnerMonth.setSelection(Calendar.getInstance().get(Calendar.MONTH))
|
||||
|
||||
binding.spinnerMonth.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val employeeId = arguments?.getString("employeeId") ?: return
|
||||
// position + 1 because months in Calendar are 0-based
|
||||
fetchAttendanceData(employeeId, position + 1)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
adapter = AttendanceAdapter()
|
||||
binding.recyclerViewAttendance.adapter = adapter
|
||||
}
|
||||
|
||||
private fun fetchAttendanceData(employeeId: String, month: Int) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
db.collection("attendance_$employeeId")
|
||||
.get()
|
||||
.addOnSuccessListener { snapshot ->
|
||||
binding.progressBar.visibility = View.GONE
|
||||
val attendanceList = snapshot.documents.mapNotNull { doc ->
|
||||
try {
|
||||
val dateParts = doc.id.split("-")
|
||||
val docMonth = dateParts[1].toInt()
|
||||
|
||||
// Only include records for selected month
|
||||
if (docMonth == month) {
|
||||
AttendanceData(
|
||||
date = doc.id,
|
||||
checkIn = doc.getString("checkIn") ?: "N/A",
|
||||
checkOut = doc.getString("checkOut") ?: "N/A"
|
||||
)
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Sort dates in descending order (newest first)
|
||||
val sortedList = attendanceList.sortedByDescending {
|
||||
try {
|
||||
val parts = it.date.split("-")
|
||||
val day = parts[0].toInt()
|
||||
val month = parts[1].toInt()
|
||||
val year = parts[2].toInt()
|
||||
String.format("%04d%02d%02d", year, month, day)
|
||||
} catch (e: Exception) {
|
||||
"00000000"
|
||||
}
|
||||
}
|
||||
|
||||
// Update count
|
||||
binding.textViewCount.text = "Total Records: ${sortedList.size}"
|
||||
|
||||
adapter.updateAttendance(sortedList)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
binding.progressBar.visibility = View.GONE
|
||||
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package `in`.sminnovations.smiadmin.ui.attendance
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.firebase.firestore.FirebaseFirestore
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
import `in`.sminnovations.smiadmin.databinding.FragmentEmployeeAttendanceListBinding
|
||||
|
||||
class EmployeeAttendanceListFragment : Fragment() {
|
||||
private var _binding: FragmentEmployeeAttendanceListBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var adapter: EmployeeListAdapter
|
||||
private val db = FirebaseFirestore.getInstance()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentEmployeeAttendanceListBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupRecyclerView()
|
||||
fetchEmployees()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
adapter = EmployeeListAdapter { employee ->
|
||||
val bundle = Bundle().apply {
|
||||
putString("employeeId", employee.Id)
|
||||
putString("employeeName", employee.Name)
|
||||
}
|
||||
findNavController().navigate(
|
||||
R.id.action_employee_list_to_attendance_detail,
|
||||
bundle
|
||||
)
|
||||
}
|
||||
binding.recyclerViewEmployees.adapter = adapter
|
||||
}
|
||||
|
||||
private fun fetchEmployees() {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
db.collection("employees")
|
||||
.get()
|
||||
.addOnSuccessListener { snapshot ->
|
||||
binding.progressBar.visibility = View.GONE
|
||||
val employees = snapshot.documents.map { doc ->
|
||||
Employee(
|
||||
Id = doc.getString("Id") ?: "Unknown",
|
||||
Name = doc.getString("Name") ?: "Unknown"
|
||||
)
|
||||
}.sortedBy { it.Name }
|
||||
adapter.updateEmployees(employees)
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
binding.progressBar.visibility = View.GONE
|
||||
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package `in`.sminnovations.smiadmin.ui.attendance
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import `in`.sminnovations.smiadmin.R
|
||||
import `in`.sminnovations.smiadmin.data.Employee
|
||||
|
||||
class EmployeeListAdapter(
|
||||
private val onEmployeeClick: (Employee) -> Unit
|
||||
) : RecyclerView.Adapter<EmployeeListAdapter.ViewHolder>() {
|
||||
|
||||
private val employees = mutableListOf<Employee>()
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val nameTextView: TextView = itemView.findViewById(R.id.textViewEmployeeName)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_employee_simple, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val employee = employees[position]
|
||||
holder.nameTextView.text = employee.Name
|
||||
holder.itemView.setOnClickListener {
|
||||
onEmployeeClick(employee)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = employees.size
|
||||
|
||||
fun updateEmployees(newEmployees: List<Employee>) {
|
||||
employees.clear()
|
||||
employees.addAll(newEmployees)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/drawable/ic_home.xml
Normal file
5
app/src/main/res/drawable/ic_home.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_settings.xml
Normal file
5
app/src/main/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||
|
||||
</vector>
|
||||
56
app/src/main/res/layout/fragment_attendance_detail.xml
Normal file
56
app/src/main/res/layout/fragment_attendance_detail.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewEmployeeName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerMonth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textViewEmployeeName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/spinnerMonth"
|
||||
tools:text="Total Records: 0" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewAttendance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spinnerMonth" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewEmployees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
23
app/src/main/res/layout/fragment_employee_list.xml
Normal file
23
app/src/main/res/layout/fragment_employee_list.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewEmployees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
103
app/src/main/res/layout/fragment_home.xml
Normal file
103
app/src/main/res/layout/fragment_home.xml
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Employee Status Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardEmployeeStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Employee Status"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="View current employee status" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Attendance Report Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardAttendanceReport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Attendance Report"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="View attendance reports" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Download Report Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardDownloadReport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Download Report"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Download attendance reports" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
50
app/src/main/res/layout/fragment_settings.xml
Normal file
50
app/src/main/res/layout/fragment_settings.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Settings"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="App Details"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Version:"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewVersion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="About:"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SMI Admin App(Employee Attendance)"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
42
app/src/main/res/layout/item_attendance.xml
Normal file
42
app/src/main/res/layout/item_attendance.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewCheckIn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewCheckOut"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
30
app/src/main/res/layout/item_employee.xml
Normal file
30
app/src/main/res/layout/item_employee.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewEmployeeName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switchStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
17
app/src/main/res/layout/item_employee_simple.xml
Normal file
17
app/src/main/res/layout/item_employee_simple.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewEmployeeName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
10
app/src/main/res/menu/bottom_nav_menu.xml
Normal file
10
app/src/main/res/menu/bottom_nav_menu.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/navigation_home"
|
||||
android:icon="@drawable/ic_home"
|
||||
android:title="Home" />
|
||||
<item
|
||||
android:id="@+id/navigation_settings"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="Settings" />
|
||||
</menu>
|
||||
50
app/src/main/res/navigation/mobile_navigation.xml
Normal file
50
app/src/main/res/navigation/mobile_navigation.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/mobile_navigation"
|
||||
app:startDestination="@+id/navigation_home">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_home"
|
||||
android:name="in.sminnovations.smiadmin.ui.HomeFragment"
|
||||
android:label="Home">
|
||||
<action
|
||||
android:id="@+id/action_home_to_employee_list"
|
||||
app:destination="@id/navigation_employee_list" />
|
||||
<action
|
||||
android:id="@+id/action_home_to_employee_attendance_list"
|
||||
app:destination="@id/navigation_employee_attendance_list" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_settings"
|
||||
android:name="in.sminnovations.smiadmin.ui.SettingsFragment"
|
||||
android:label="Settings" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_employee_list"
|
||||
android:name="in.sminnovations.smiadmin.ui.EmployeeListFragment"
|
||||
android:label="Employee List" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_employee_attendance_list"
|
||||
android:name="in.sminnovations.smiadmin.ui.attendance.EmployeeAttendanceListFragment"
|
||||
android:label="Employee List">
|
||||
<action
|
||||
android:id="@+id/action_employee_list_to_attendance_detail"
|
||||
app:destination="@id/navigation_attendance_detail" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_attendance_detail"
|
||||
android:name="in.sminnovations.smiadmin.ui.attendance.AttendanceDetailFragment"
|
||||
android:label="Attendance Detail">
|
||||
<argument
|
||||
android:name="employeeId"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="employeeName"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
6
app/src/main/res/xml/provider_paths.xml
Normal file
6
app/src/main/res/xml/provider_paths.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-files-path
|
||||
name="external_files"
|
||||
path="." />
|
||||
</paths>
|
||||
Reference in New Issue
Block a user