changes according to smi + iisc, added login screen, added filer, improved the UI

This commit is contained in:
chandrashekhar reddy
2025-02-04 17:27:33 +05:30
parent 830d347606
commit 43c3137041
29 changed files with 699 additions and 46 deletions

11
.idea/other.xml generated
View File

@@ -124,6 +124,17 @@
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm2q" />
<option name="id" value="dm2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />

View File

@@ -13,8 +13,8 @@ android {
applicationId "in.sminnovations.smiadmin"
minSdk 26
targetSdk 34
versionCode 3
versionName "1.3"
versionCode 4
versionName "1.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -17,9 +17,9 @@
android:requestLegacyExternalStorage="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:icon="@drawable/smi_logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@drawable/smi_logo"
android:supportsRtl="true"
android:theme="@style/Theme.SMIAdmin"
tools:targetApi="31">

View File

@@ -1,6 +1,7 @@
package `in`.sminnovations.smiadmin
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
@@ -29,7 +30,21 @@ class MainActivity : AppCompatActivity() {
navController = navHostFragment.navController
// Setup the bottom navigation with the NavController
binding.navView.setupWithNavController(navController)
binding.navView?.let { bottomNav ->
bottomNav.setupWithNavController(navController)
// Hide bottom nav on specific destinations
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.navigation_login -> {
bottomNav.visibility = View.GONE
}
else -> {
bottomNav.visibility = View.VISIBLE
}
}
}
}
// Set up navigation item selection listener
binding.navView.setOnItemSelectedListener { menuItem ->

View File

@@ -2,7 +2,7 @@ package `in`.sminnovations.smiadmin.data
data class Employee(
var Id: String = "",
val Name: String = "",
var Name: String = "",
var EmployeeStatus: Boolean = false,
var Designation:String = "",
var DateOfJoining: String = "",
@@ -10,4 +10,6 @@ data class Employee(
var Mobile: String = "",
var Email: String = "",
var CreatedAt: String = "",
var from: String="",
var role:String= "user"
)

View File

@@ -10,7 +10,7 @@ import `in`.sminnovations.smiadmin.R
import `in`.sminnovations.smiadmin.data.Employee
class EmployeeListAdapter(
private val employees: MutableList<Employee> = mutableListOf(),
val employees: MutableList<Employee> = mutableListOf(),
private val onStatusChanged: (Employee, Boolean) -> Unit
) : RecyclerView.Adapter<EmployeeListAdapter.EmployeeViewHolder>() {
@@ -32,7 +32,7 @@ class EmployeeListAdapter(
holder.statusSwitch.setOnCheckedChangeListener(null)
// Set the text and switch state
holder.nameTextView.text = employee.Name
holder.nameTextView.text = employee.Name + " (" + employee.from + ")"
holder.statusSwitch.isChecked = employee.EmployeeStatus
// Set up the new listener after setting the checked state

View File

@@ -34,7 +34,7 @@ class EmployeeQrAdapter(
fun bind(employee: Employee) {
binding.apply {
textEmployeeName.text = employee.Name
textEmployeeName.text = employee.Name + " (" + employee.from + ")"
textEmployeeId.text = employee.Id
buttonShare.setOnClickListener { onShareClick(employee) }

View File

@@ -26,7 +26,7 @@ class EmployeeStatusListAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val employee = employees[position]
holder.nameTextView.text = employee.Name
holder.nameTextView.text = employee.Name + " (" + employee.from + ")"
holder.itemView.setOnClickListener {
onEmployeeClick(employee)
}

View File

@@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.navigation.fragment.findNavController
import com.google.firebase.firestore.FirebaseFirestore
@@ -38,7 +39,12 @@ class AddEmployeeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val items = arrayOf("IISc", "SMI")
val adapter = ArrayAdapter(requireActivity(), R.layout.list_item, items)
binding.etEmployeeFrom.setAdapter(adapter)
binding.etEmployeeFrom.isFocusable = false
binding.etEmployeeFrom.isClickable = true
setupDatePickers()
setupSubmitButton()
}
@@ -138,7 +144,8 @@ class AddEmployeeFragment : Fragment() {
Designation = binding.etEmployeePosition.text.toString().trim(),
dateOfBirth = dateFormatter.format(dateOfBirth?.time ?: Date()),
DateOfJoining = dateFormatter.format(dateOfJoining?.time ?: Date()),
CreatedAt = dateFormatter.format(Date()) // Current date in dd/MM/yyyy format
CreatedAt = dateFormatter.format(Date()), // Current date in dd/MM/yyyy format
from = binding.etEmployeeFrom.text.toString().trim(),
)
db.collection("employees")

View File

@@ -1,7 +1,9 @@
package `in`.sminnovations.smiadmin.ui.fragments
import android.Manifest
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -12,6 +14,8 @@ import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.RadioGroup
import android.widget.Toast
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
@@ -64,12 +68,39 @@ class HomeFragment : Fragment() {
private fun showMonthYearPicker() {
MonthYearPickerDialog().apply {
setOnDateSelectedListener { year, month ->
downloadAttendanceReport(year, month)
showReportDialog(year, month, requireActivity())
//downloadAttendanceReport(year, month)
}
}.show(parentFragmentManager, "MonthYearPicker")
}
private fun showReportDialog(year: Int, month: Int, context: Context) {
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_report_source, null)
val dialog = AlertDialog.Builder(context)
.setView(dialogView)
.setCancelable(true)
.create()
private fun downloadAttendanceReport(year: Int, month: Int) {
val radioGroup = dialogView.findViewById<RadioGroup>(R.id.rgSource)
val downloadButton = dialogView.findViewById<Button>(R.id.btnDownload)
downloadButton.setOnClickListener {
val selectedSource = when (radioGroup.checkedRadioButtonId) {
R.id.rbSMI -> "SMI"
R.id.rbIISC -> "IISc"
else -> null
}
if (selectedSource != null) {
downloadAttendanceReport(year, month, selectedSource)
dialog.dismiss()
} else {
Toast.makeText(context, "Please select a source", Toast.LENGTH_SHORT).show()
}
}
dialog.show()
}
private fun downloadAttendanceReport(year: Int, month: Int, source: String) {
currentExcelTask?.cancel()
val progressDialog = ProgressDialog(requireContext()).apply {
@@ -77,7 +108,7 @@ class HomeFragment : Fragment() {
setCancelable(false)
show()
}
exportTask.exportAttendance(year, month)
exportTask.exportAttendance(year, month,source)
progressDialog.dismiss()
}

View File

@@ -0,0 +1,175 @@
package `in`.sminnovations.smiadmin.ui.fragments
import android.content.Context
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.android.material.bottomnavigation.BottomNavigationView
import com.google.firebase.firestore.FirebaseFirestore
import `in`.sminnovations.smiadmin.MainActivity
import `in`.sminnovations.smiadmin.R
import `in`.sminnovations.smiadmin.databinding.FragmentLoginBinding
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private lateinit var db: FirebaseFirestore
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
db = FirebaseFirestore.getInstance()
// Check if already logged in
val prefs = requireActivity().getSharedPreferences("admin_prefs", Context.MODE_PRIVATE)
val isLoggedIn = prefs.getBoolean("is_logged_in", false)
if (isLoggedIn) {
// Verify stored credentials in Firestore
binding.childLayout.visibility = View.GONE
binding.parentLayout.visibility = View.VISIBLE
val adminMobile = prefs.getString("admin_mobile", "") ?: ""
verifyAndNavigate(adminMobile)
} else {
binding.childLayout.visibility = View.VISIBLE
binding.parentLayout.visibility = View.GONE
}
binding.btnLogin.setOnClickListener {
val mobile = binding.etMobile.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
when {
mobile.isEmpty() -> {
binding.etMobile.error = "Please enter mobile number"
return@setOnClickListener
}
mobile.length != 10 -> {
binding.etMobile.error = "Please enter valid mobile number"
return@setOnClickListener
}
password.isEmpty() -> {
binding.etPassword.error = "Please enter password"
return@setOnClickListener
}
else -> attemptLogin(mobile, password)
}
}
}
private fun verifyAndNavigate(mobile: String) {
if (mobile.isEmpty()) {
// If stored mobile is empty, clear prefs and show login
requireActivity().getSharedPreferences("admin_prefs", Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
return
}
db.collection("employees")
.whereEqualTo("mobile", mobile)
.whereEqualTo("role", "admin")
.get()
.addOnSuccessListener { documents ->
if (!documents.isEmpty) {
// Still valid admin, navigate to home
navigateToHome()
} else {
// No longer an admin, clear prefs
requireActivity().getSharedPreferences("admin_prefs", Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
}
}
.addOnFailureListener {
// On error, stay on login screen
showError("Error verifying credentials")
}
}
private fun attemptLogin(mobile: String, password: String) {
showLoading(true)
db.collection("employees")
.whereEqualTo("mobile", mobile)
.whereEqualTo("role", "admin")
.get()
.addOnSuccessListener { documents ->
if (documents.isEmpty) {
showLoading(false)
showError("Invalid credentials or not an admin user")
return@addOnSuccessListener
}
val document = documents.first()
val storedPassword = document.getString("password") ?: document.getString("Password")
if (storedPassword == password) {
// Save login state
val editor = requireActivity().getSharedPreferences(
"admin_prefs",
Context.MODE_PRIVATE
).edit()
editor.putBoolean("is_logged_in", true)
editor.putString("admin_id", document.id)
editor.putString("admin_mobile", mobile)
editor.apply()
showLoading(false)
navigateToHome()
} else {
showLoading(false)
showError("Invalid password")
}
}
.addOnFailureListener { e ->
showLoading(false)
showError("Error: ${e.message}")
}
}
private fun showLoading(show: Boolean) {
binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
binding.btnLogin.isEnabled = !show
}
private fun showError(message: String) {
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}
private fun navigateToHome() {
findNavController().navigate(R.id.action_login_to_home)
}
override fun onResume() {
super.onResume()
// Hide bottom navigation
(activity as? MainActivity)?.findViewById<BottomNavigationView>(R.id.nav_view)?.visibility = View.GONE
}
override fun onStop() {
super.onStop()
// Show bottom navigation when leaving fragment
(activity as? MainActivity)?.findViewById<BottomNavigationView>(R.id.nav_view)?.visibility = View.VISIBLE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,5 +1,6 @@
package `in`.sminnovations.smiadmin.ui.fragments
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
@@ -7,6 +8,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.button.MaterialButton
import `in`.sminnovations.smiadmin.R
@@ -20,6 +23,10 @@ class SettingsFragment : Fragment() {
// Set app version
val versionTextView: TextView = root.findViewById(R.id.textViewVersion)
val button: MaterialButton = root.findViewById(R.id.logOutBtn)
button.setOnClickListener {
logout()
}
try {
val pInfo = requireContext().packageManager.getPackageInfo(
requireContext().packageName, 0
@@ -34,4 +41,12 @@ class SettingsFragment : Fragment() {
return root
}
private fun logout() {
requireActivity().getSharedPreferences("admin_prefs", Context.MODE_PRIVATE)
.edit()
.clear()
.apply()
findNavController().navigate(R.id.action_settings_to_login)
}
}

View File

@@ -1,5 +1,6 @@
package `in`.sminnovations.smiadmin.ui.fragments
import android.R
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
@@ -9,6 +10,8 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.Toast
import androidx.core.content.FileProvider
@@ -28,6 +31,7 @@ class ShareQrFragment : Fragment() {
private lateinit var binding: FragmentShareQrBinding
private lateinit var employeeAdapter: EmployeeQrAdapter
private val db = FirebaseFirestore.getInstance()
private var employeeList = emptyList<Employee>()
override fun onCreateView(
inflater: LayoutInflater,
@@ -41,8 +45,57 @@ class ShareQrFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupSpinner()
setupSearchView()
fetchEmployees()
}
private fun setupSearchView() {
binding.searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
filterEmployees(newText)
return true
}
})
}
private fun filterEmployees(query: String?) {
if (query.isNullOrBlank()) {
employeeAdapter.submitList(employeeList)
return
}
val filteredList = employeeList.filter {
it.Name.contains(query, ignoreCase = true)
}
employeeAdapter.submitList(filteredList)
}
private fun setupSpinner() {
val sortOptions = arrayOf("Name (A-Z)", "SMI First", "IISc First")
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, sortOptions)
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
binding.spinnerSort.adapter = adapter
binding.spinnerSort.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
sortEmployees()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
private fun sortEmployees() {
val currentList = employeeAdapter.currentList.toMutableList()
when (binding.spinnerSort.selectedItemPosition) {
0 -> currentList.sortBy { it.Name?.lowercase() ?: "" }
1 -> currentList.sortByDescending { it.from == "SMI" } // Changed to sortByDescending
2 -> currentList.sortByDescending { it.from == "IISc" } // Changed to sortByDescending
}
employeeAdapter.submitList(currentList)
}
private fun setupRecyclerView() {
employeeAdapter = EmployeeQrAdapter(
@@ -64,13 +117,19 @@ class ShareQrFragment : Fragment() {
return@addSnapshotListener
}
val employeeList = snapshot?.documents?.mapNotNull { doc ->
doc.toObject(Employee::class.java)?.takeIf { it.EmployeeStatus }?.apply {
employeeList = snapshot?.documents?.mapNotNull { doc ->
val employee = doc.toObject(Employee::class.java)
employee?.apply {
Id = doc.id
Name = doc.getString("name") ?: doc.getString("Name") ?: this.Name
from = doc.getString("from") ?: ""
}
} ?: emptyList()
employeeAdapter.submitList(employeeList)
// Sorting manually to handle both "name" and "Name"
val sortedList = employeeList.sortedBy { it.Name?.lowercase() }
employeeAdapter.submitList(sortedList)
}
}

View File

@@ -24,6 +24,9 @@ class AttendanceDetailFragment : Fragment() {
private val months = arrayOf("January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December")
// Add years (you might want to adjust the range based on your needs)
private val years = (2024..2030).toList()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -41,11 +44,13 @@ class AttendanceDetailFragment : Fragment() {
binding.textViewEmployeeName.text = employeeName
setupMonthSpinner()
setupYearSpinner()
setupRecyclerView()
// Get current month (1-12)
// Get current month (1-12) and year
val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1
fetchAttendanceData(employeeId, currentMonth)
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
fetchAttendanceData(employeeId, currentMonth, currentYear)
}
private fun setupMonthSpinner() {
@@ -63,8 +68,35 @@ class AttendanceDetailFragment : Fragment() {
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)
val selectedYear = years[binding.spinnerYear.selectedItemPosition]
fetchAttendanceData(employeeId, position + 1, selectedYear)
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
private fun setupYearSpinner() {
val adapter = ArrayAdapter(
requireContext(),
R.layout.simple_spinner_item,
years
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerYear.adapter = adapter
// Set current year as default
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
val currentYearPosition = years.indexOf(currentYear)
if (currentYearPosition != -1) {
binding.spinnerYear.setSelection(currentYearPosition)
}
binding.spinnerYear.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val employeeId = arguments?.getString("employeeId") ?: return
val selectedMonth = binding.spinnerMonth.selectedItemPosition + 1
fetchAttendanceData(employeeId, selectedMonth, years[position])
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
@@ -76,7 +108,7 @@ class AttendanceDetailFragment : Fragment() {
binding.recyclerViewAttendance.adapter = adapter
}
private fun fetchAttendanceData(employeeId: String, month: Int) {
private fun fetchAttendanceData(employeeId: String, month: Int, year: Int) {
binding.progressBar.visibility = View.VISIBLE
db.collection("attendance_$employeeId")
@@ -87,9 +119,10 @@ class AttendanceDetailFragment : Fragment() {
try {
val dateParts = doc.id.split("-")
val docMonth = dateParts[1].toInt()
val docYear = dateParts[2].toInt()
// Only include records for selected month
if (docMonth == month) {
// Only include records for selected month and year
if (docMonth == month && docYear == year) {
AttendanceData(
date = doc.id,
checkIn = doc.getString("checkIn") ?: "N/A",

View File

@@ -19,6 +19,7 @@ class EmployeeAttendanceListFragment : Fragment() {
private lateinit var adapter: EmployeeStatusListAdapter
private val db = FirebaseFirestore.getInstance()
private var allEmployees = listOf<Employee>() // Store all employees
override fun onCreateView(
inflater: LayoutInflater,
@@ -33,9 +34,35 @@ class EmployeeAttendanceListFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
setupSearchView()
fetchEmployees()
}
private fun setupSearchView() {
binding.searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
filterEmployees(newText)
return true
}
})
}
private fun filterEmployees(query: String?) {
if (query.isNullOrBlank()) {
adapter.updateEmployees(allEmployees)
return
}
val filteredList = allEmployees.filter {
it.Name.contains(query, ignoreCase = true)
}
adapter.updateEmployees(filteredList)
}
private fun setupRecyclerView() {
adapter = EmployeeStatusListAdapter { employee ->
val bundle = Bundle().apply {
@@ -57,18 +84,19 @@ class EmployeeAttendanceListFragment : Fragment() {
.get()
.addOnSuccessListener { snapshot ->
binding.progressBar.visibility = View.GONE
val employees = snapshot.documents.mapNotNull { doc ->
allEmployees = snapshot.documents.mapNotNull { doc ->
val status = doc.getBoolean("EmployeeStatus") ?: true // Default to true if field is missing
if (status) {
Employee(
Id = doc.getString("Id") ?: doc.getString("id") ?: "",
Name = doc.getString("Name") ?: doc.getString("name") ?: ""
Name = doc.getString("Name") ?: doc.getString("name") ?: "",
from = doc.getString("from") ?: ""
)
} else {
null // Exclude employees with EmployeeStatus == false
}
}.sortedBy { it.Name }
adapter.updateEmployees(employees)
adapter.updateEmployees(allEmployees)
}
.addOnFailureListener { e ->
binding.progressBar.visibility = View.GONE

View File

@@ -1,9 +1,12 @@
package `in`.sminnovations.smiadmin.ui.fragments.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
@@ -15,7 +18,7 @@ import `in`.sminnovations.smiadmin.databinding.FragmentEmployeeListBinding
class EmployeeListFragment : Fragment() {
private var _binding: FragmentEmployeeListBinding? = null
private val binding get() = _binding!!
private var employeeList = emptyList<Employee>()
private lateinit var adapter: EmployeeListAdapter
private val db = FirebaseFirestore.getInstance()
@@ -30,11 +33,58 @@ class EmployeeListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupSpinner()
setupRecyclerView()
setupSearchView()
fetchEmployees()
}
private fun setupSearchView() {
binding.searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
filterEmployees(newText)
return true
}
})
}
private fun filterEmployees(query: String?) {
if (query.isNullOrBlank()) {
adapter.updateEmployees(employeeList)
return
}
val filteredList = employeeList.filter {
it.Name.contains(query, ignoreCase = true)
}
adapter.updateEmployees(filteredList)
}
private fun setupSpinner() {
val sortOptions = arrayOf("Name (A-Z)", "SMI First", "IISc First")
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, sortOptions)
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
binding.spinnerSort.adapter = adapter
binding.spinnerSort.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
sortEmployees()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
private fun sortEmployees() {
val currentList = adapter.employees.toMutableList()
when (binding.spinnerSort.selectedItemPosition) {
0 -> currentList.sortBy { it.Name?.lowercase() ?: "" }
1 -> currentList.sortByDescending { it.from == "SMI" } // Changed to sortByDescending
2 -> currentList.sortByDescending { it.from == "IISc" } // Changed to sortByDescending
}
adapter.updateEmployees(currentList)
}
private fun setupRecyclerView() {
adapter = EmployeeListAdapter { employee, newStatus ->
updateEmployeeStatus(employee, newStatus)
@@ -56,13 +106,14 @@ class EmployeeListFragment : Fragment() {
}
snapshot?.let {
val employeeList = snapshot.documents.map { doc ->
employeeList = snapshot.documents.map { doc ->
Employee(
Id = doc.getString("Id") ?: doc.getString("id") ?: "",
Name = doc.getString("Name") ?: doc.getString("name") ?: "",
EmployeeStatus = doc.getBoolean("EmployeeStatus") ?: doc.getBoolean("employeestatus") ?: false
EmployeeStatus = doc.getBoolean("EmployeeStatus") ?: false,
from = doc.getString("from") ?: ""
)
}
}.sortedBy { it.Name?.lowercase() }
adapter.updateEmployees(employeeList)
}
}

View File

@@ -14,6 +14,7 @@ import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.google.firebase.Firebase
import com.google.firebase.firestore.Source
import com.google.firebase.firestore.firestore
import `in`.sminnovations.smiadmin.data.Constant
import `in`.sminnovations.smiadmin.data.Employee
@@ -41,7 +42,7 @@ class ExportTask(private val fragment: Fragment) {
}
}
fun exportAttendance(year: Int, month: Int) {
fun exportAttendance(year: Int, month: Int, source: String) {
if (!checkStoragePermission()) {
ActivityCompat.requestPermissions(
fragment.requireActivity(),
@@ -62,24 +63,30 @@ class ExportTask(private val fragment: Fragment) {
currentExcelTask = fragment.lifecycleScope.launch(Dispatchers.Main) {
try {
val company = if (source == "SMI"){
Constant.COMPANY_NAME
}else{
"IISc-OMI_LAB"
}
// First get all employees
val employeesSnapshot = withContext(Dispatchers.IO) {
db.collection("employees")
.whereEqualTo("EmployeeStatus", true)
.whereEqualTo("from",source)
.get()
.await()
}
val employees = employeesSnapshot.documents.map { doc ->
Employee(
Id = doc.getString("Id") ?: "Unknown",
Name = doc.getString("Name") ?: "Unknown"
Id = doc.getString("Id") ?: doc.getString("id") ?: "",
Name = doc.getString("Name") ?: doc.getString("name") ?: "",
)
}.sortedBy { it.Name }
withContext(Dispatchers.IO) {
val pdfGenerator = PDFGenerator(fragment.requireContext(), Constant.COMPANY_NAME)
pdfGenerator.generateAttendanceReport(employees, year, month)
val pdfGenerator = PDFGenerator(fragment.requireContext(), company)
pdfGenerator.generateAttendanceReport(employees, year, month,source)
}.also { pdfFile ->
withContext(Dispatchers.IO) {
try {

View File

@@ -47,7 +47,8 @@ class PDFGenerator(
suspend fun generateAttendanceReport(
employees: List<Employee>,
selectedYear: Int,
selectedMonth: Int
selectedMonth: Int,
source: String
): File {
document = PdfDocument()
createNewPage()

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Report Source"
android:textSize="18sp"
android:textStyle="bold"
android:paddingBottom="8dp" />
<RadioGroup
android:id="@+id/rgSource"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/rbSMI"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SMI" />
<RadioButton
android:id="@+id/rbIISC"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IISC" />
</RadioGroup>
<Button
android:id="@+id/btnDownload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Download" />
</LinearLayout>

View File

@@ -66,6 +66,21 @@
android:hint="Designation" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/etEmployeeFrom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:hint="Working From" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -19,10 +19,17 @@
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" />
<Spinner
android:id="@+id/spinnerYear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/spinnerMonth"
app:layout_constraintTop_toTopOf="@id/spinnerMonth" />
<TextView
android:id="@+id/textViewCount"
android:layout_width="wrap_content"

View File

@@ -4,11 +4,22 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent"
app:iconifiedByDefault="false"
app:queryHint="Search by name"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewEmployees"
android:layout_marginTop="15dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_marginTop="15dp"
app:layout_constraintTop_toBottomOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<ProgressBar

View File

@@ -3,12 +3,28 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent"
app:iconifiedByDefault="false"
app:queryHint="Search by name"/>
<Spinner
android:id="@+id/spinnerSort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toBottomOf="@+id/searchView" />
<androidx.recyclerview.widget.RecyclerView
android:layout_marginTop="15dp"
android:id="@+id/recyclerViewEmployees"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/spinnerSort"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<ProgressBar

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:layout_gravity="center"
>
<LinearLayout
android:id="@+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/smi_logo"
android:layout_margin="15dp"
android:orientation="vertical"
android:padding="16dp"
android:visibility="visible"
android:gravity="center">
</LinearLayout>
<LinearLayout
android:id="@+id/childLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:visibility="gone"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Admin Login"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etMobile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Mobile Number"
android:inputType="phone"
android:maxLength="10"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:padding="12dp"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

View File

@@ -47,4 +47,11 @@
android:text="SMI Admin App(Employee Attendance)"
android:layout_marginTop="4dp" />
<com.google.android.material.button.MaterialButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Logout"
android:layout_marginTop="30dp"
android:id="@+id/logOutBtn"/>
</LinearLayout>

View File

@@ -4,7 +4,20 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent"
app:iconifiedByDefault="false"
app:queryHint="Search by name"/>
<Spinner
android:id="@+id/spinnerSort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toBottomOf="@+id/searchView" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewEmployees"
@@ -14,7 +27,7 @@
android:padding="8dp"
android:layout_marginTop="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinnerSort"
tools:listitem="@layout/item_employee_qr" />
<ProgressBar

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceSubtitle1" />

View File

@@ -1,8 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- mobile_navigation.xml -->
<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">
app:startDestination="@+id/navigation_login">
<fragment
android:id="@+id/navigation_login"
android:name="in.sminnovations.smiadmin.ui.fragments.LoginFragment"
android:label="Login">
<action
android:id="@+id/action_login_to_home"
app:destination="@id/navigation_home"
app:popUpTo="@id/navigation_login"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/navigation_home"
@@ -25,7 +37,13 @@
<fragment
android:id="@+id/navigation_settings"
android:name="in.sminnovations.smiadmin.ui.fragments.SettingsFragment"
android:label="Settings" />
android:label="Settings">
<action
android:id="@+id/action_settings_to_login"
app:destination="@id/navigation_login"
app:popUpTo="@id/mobile_navigation"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/navigation_employee_list"
@@ -51,6 +69,7 @@
app:popUpTo="@id/navigation_home"
app:popUpToInclusive="false" />
</fragment>
<fragment
android:id="@+id/navigation_employee_qr"
android:name="in.sminnovations.smiadmin.ui.fragments.ShareQrFragment"