diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 31d6a312a..adf1f38a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -187,6 +187,7 @@
+
diff --git a/app/src/main/java/deakin/gopher/guardian/adapter/PatientLogAdapter.kt b/app/src/main/java/deakin/gopher/guardian/adapter/PatientLogAdapter.kt
new file mode 100644
index 000000000..4ecb2df08
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/adapter/PatientLogAdapter.kt
@@ -0,0 +1,70 @@
+package deakin.gopher.guardian.adapter
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import deakin.gopher.guardian.databinding.ItemPatientLogBinding
+import deakin.gopher.guardian.model.PatientLog
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+class PatientLogAdapter(
+ private var logs: List,
+ private val onDeleteClick: (PatientLog) -> Unit
+) : RecyclerView.Adapter() {
+
+ inner class ViewHolder(val binding: ItemPatientLogBinding) :
+ RecyclerView.ViewHolder(binding.root)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val binding = ItemPatientLogBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ return ViewHolder(binding)
+ }
+
+ override fun getItemCount() = logs.size
+
+ @SuppressLint("SetTextI18n")
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val log = logs[position]
+
+ holder.binding.title.text = log.title
+ holder.binding.description.text = log.description
+ holder.binding.meta.text =
+ "${log.createdBy.fullname} • ${formatDate(log.createdAt)}"
+
+ holder.binding.deleteBtn.setOnClickListener {
+ onDeleteClick(log)
+ }
+ }
+
+ fun updateData(newLogs: List) {
+ logs = newLogs
+ notifyDataSetChanged()
+ }
+
+ private fun formatDate(dateString: String): String {
+
+ return try {
+
+ val inputFormat =
+ SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
+
+ inputFormat.timeZone = TimeZone.getTimeZone("UTC")
+
+ val outputFormat =
+ SimpleDateFormat("dd MMM yyyy, hh:mm a", Locale.getDefault())
+
+ val date = inputFormat.parse(dateString)
+
+ outputFormat.format(date!!)
+
+ } catch (e: Exception) {
+
+ dateString
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/model/CreatePatientLogRequest.kt b/app/src/main/java/deakin/gopher/guardian/model/CreatePatientLogRequest.kt
new file mode 100644
index 000000000..3acd2624e
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/model/CreatePatientLogRequest.kt
@@ -0,0 +1,7 @@
+package deakin.gopher.guardian.model
+
+data class CreatePatientLogRequest(
+ val patient: String,
+ val title: String,
+ val description: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/model/PatientLog.kt b/app/src/main/java/deakin/gopher/guardian/model/PatientLog.kt
new file mode 100644
index 000000000..dec07776b
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/model/PatientLog.kt
@@ -0,0 +1,16 @@
+package deakin.gopher.guardian.model
+
+data class PatientLog(
+ val _id: String,
+ val patient: String,
+ val title: String,
+ val description: String,
+ val createdBy: CreatedBy,
+ val createdAt: String
+)
+
+data class CreatedBy(
+ val _id: String,
+ val fullname: String,
+ val role: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/services/NavigationService.kt b/app/src/main/java/deakin/gopher/guardian/services/NavigationService.kt
index c0b2775d5..eb601553d 100644
--- a/app/src/main/java/deakin/gopher/guardian/services/NavigationService.kt
+++ b/app/src/main/java/deakin/gopher/guardian/services/NavigationService.kt
@@ -14,6 +14,7 @@ import deakin.gopher.guardian.view.general.RegisterActivity
import deakin.gopher.guardian.view.general.Setting
import deakin.gopher.guardian.view.general.TaskAddActivity
import deakin.gopher.guardian.view.general.TasksListActivity
+import deakin.gopher.guardian.view.patient.PatientLogsActivity
class NavigationService(val activity: Activity) {
fun toHomeScreenForRole(role: Role) {
@@ -74,6 +75,15 @@ class NavigationService(val activity: Activity) {
)
}
+ fun onPatientLogs() {
+ activity.startActivity(
+ Intent(
+ activity.applicationContext,
+ PatientLogsActivity::class.java
+ )
+ )
+ }
+
fun onSignOut() {
activity.startActivity(
Intent(
diff --git a/app/src/main/java/deakin/gopher/guardian/services/PatientLogService.kt b/app/src/main/java/deakin/gopher/guardian/services/PatientLogService.kt
new file mode 100644
index 000000000..4faf0b24c
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/services/PatientLogService.kt
@@ -0,0 +1,4 @@
+package deakin.gopher.guardian.services
+
+class PatientLogService {
+}
\ No newline at end of file
diff --git a/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt b/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
index 0e9ea177e..f53bffbbc 100644
--- a/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
+++ b/app/src/main/java/deakin/gopher/guardian/services/api/ApiService.kt
@@ -3,10 +3,12 @@ package deakin.gopher.guardian.services.api
import deakin.gopher.guardian.model.AddPatientActivityResponse
import deakin.gopher.guardian.model.AddPatientResponse
import deakin.gopher.guardian.model.BaseModel
+import deakin.gopher.guardian.model.CreatePatientLogRequest
import deakin.gopher.guardian.model.Patient
import deakin.gopher.guardian.model.PatientActivity
import deakin.gopher.guardian.model.register.AuthResponse
import deakin.gopher.guardian.model.register.RegisterRequest
+import deakin.gopher.guardian.model.PatientLog
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
@@ -91,4 +93,23 @@ interface ApiService {
@Header("Authorization") token: String,
@Path("id") patientId: String,
): Response
+
+ //For Patient Logs
+ @GET("patient-logs/{patientId}")
+ suspend fun getPatientLogs(
+ @Header("Authorization") token: String,
+ @Path("patientId") patientId: String,
+ ): Response>
+
+ @POST("patient-logs")
+ suspend fun createPatientLog(
+ @Header("Authorization") token: String,
+ @Body log: CreatePatientLogRequest,
+ ): Response
+
+ @DELETE("patient-logs/{id}")
+ suspend fun deletePatientLog(
+ @Header("Authorization") token: String,
+ @Path("id") id: String,
+ ): Response
}
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/AddPatientLogActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/general/AddPatientLogActivity.kt
index ac9838227..af1be0d82 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/AddPatientLogActivity.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/AddPatientLogActivity.kt
@@ -1,15 +1,11 @@
package deakin.gopher.guardian.view.general
-import android.app.DatePickerDialog
-import android.app.TimePickerDialog
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Toast
-import com.google.gson.Gson
-import deakin.gopher.guardian.R
import deakin.gopher.guardian.databinding.ActivityAddPatientActivityBinding
-import deakin.gopher.guardian.model.ApiErrorResponse
+import deakin.gopher.guardian.model.CreatePatientLogRequest
import deakin.gopher.guardian.model.login.SessionManager
import deakin.gopher.guardian.services.api.ApiClient
import deakin.gopher.guardian.view.hide
@@ -57,48 +53,39 @@ class AddPatientLogActivity : BaseActivity() {
}
// Set default date and time
- val calendar = Calendar.getInstance()
- val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
- val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
-
- binding.txtDate.setText(dateFormat.format(calendar.time))
- binding.txtTime.setText(timeFormat.format(calendar.time))
-
- // Date picker dialog
- binding.txtDate.setOnClickListener {
- val dpd =
- DatePickerDialog(
- this,
- { _, year, month, dayOfMonth ->
- calendar.set(year, month, dayOfMonth)
- binding.txtDate.setText(dateFormat.format(calendar.time))
- },
- calendar.get(Calendar.YEAR),
- calendar.get(Calendar.MONTH),
- calendar.get(Calendar.DAY_OF_MONTH),
- )
- dpd.datePicker.maxDate = System.currentTimeMillis()
- dpd.show()
- }
+ // Keep current date/time visible but prevent editing
+ binding.txtDate.isFocusable = false
+ binding.txtDate.isClickable = false
+
+ binding.txtTime.isFocusable = false
+ binding.txtTime.isClickable = false
+
+// Live updating current date and time
+ val handler = android.os.Handler(mainLooper)
+
+ val updateTimeRunnable = object : Runnable {
+
+ override fun run() {
+
+ val currentCalendar = Calendar.getInstance()
+
+ val currentDate =
+ SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ .format(currentCalendar.time)
+
+ val currentTime =
+ SimpleDateFormat("HH:mm:ss", Locale.getDefault())
+ .format(currentCalendar.time)
+
+ binding.txtDate.setText(currentDate)
+ binding.txtTime.setText(currentTime)
- // Time picker dialog
- binding.txtTime.setOnClickListener {
- val tpd =
- TimePickerDialog(this, { _, hour, minute ->
- calendar.set(Calendar.HOUR_OF_DAY, hour)
- calendar.set(Calendar.MINUTE, minute)
- binding.txtTime.setText(
- String.format(
- Locale.getDefault(),
- "%02d:%02d",
- hour,
- minute,
- ),
- )
- }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true)
- tpd.show()
+ handler.postDelayed(this, 1000)
+ }
}
+ handler.post(updateTimeRunnable)
+
binding.txtReporter.setText(SessionManager.getCurrentUser().name)
binding.btnSave.setOnClickListener {
@@ -107,62 +94,75 @@ class AddPatientLogActivity : BaseActivity() {
}
private fun savePatientActivity() {
- val activityType =
+
+ val title =
if (binding.txtActivityType.text.toString().trim() == "Other") {
binding.txtOtherActivity.text.toString().trim()
} else {
binding.txtActivityType.text.toString().trim()
}
- if (activityType.trim().isEmpty()) {
- showMessage(getString(R.string.validation_empty_activity_type))
+ val description = binding.txtComment.text.toString().trim()
+
+ if (title.isEmpty()) {
+ showMessage("Please enter title")
return
}
- val date = binding.txtDate.text.toString()
- val time = binding.txtTime.text.toString()
- // Build ISO timestamp
- val isoTimestamp = "${date}T$time:00Z"
-
- val comments = binding.txtComment.text.toString().trim()
+ if (description.isEmpty()) {
+ showMessage("Please enter description")
+ return
+ }
val patientId = intent.getStringExtra("patientId").orEmpty()
- val token = "Bearer ${SessionManager.getToken()}"
+
+ val request = CreatePatientLogRequest(
+ patient = patientId,
+ title = title,
+ description = description
+ )
+
CoroutineScope(Dispatchers.IO).launch {
+
withContext(Dispatchers.Main) {
binding.progressBar.show()
binding.btnSave.visibility = View.GONE
}
- val response =
- ApiClient.apiService.logPatientActivity(
- token,
- patientId,
- activityType,
- isoTimestamp,
- comments,
+ try {
+
+ val response = ApiClient.apiService.createPatientLog(
+ "Bearer ${SessionManager.getToken()}",
+ request
)
- withContext(Dispatchers.Main) {
withContext(Dispatchers.Main) {
+
binding.progressBar.hide()
binding.btnSave.visibility = View.VISIBLE
- }
- if (response.isSuccessful) {
- if (response.body() != null) {
- showMessage(response.body()?.apiMessage ?: response.message())
- onBackPressedDispatcher.onBackPressed()
+
+ if (response.isSuccessful) {
+
+ showMessage("Log created successfully")
+
+ finish()
+
} else {
- showMessage(response.body()?.apiError ?: "Failed to add patient activity")
- }
- } else {
- // Handle error
- val errorResponse =
- Gson().fromJson(
- response.errorBody()?.string(),
- ApiErrorResponse::class.java,
+
+ showMessage(
+ response.errorBody()?.string() ?: "Failed to create log"
)
- showMessage(errorResponse.apiError ?: response.message())
+ }
+ }
+
+ } catch (e: Exception) {
+
+ withContext(Dispatchers.Main) {
+
+ binding.progressBar.hide()
+ binding.btnSave.visibility = View.VISIBLE
+
+ showMessage(e.message ?: "Unknown error")
}
}
}
diff --git a/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt b/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
index 4b529e376..64d6d4c9a 100644
--- a/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
+++ b/app/src/main/java/deakin/gopher/guardian/view/general/Homepage4nurse.kt
@@ -21,11 +21,17 @@ class Homepage4nurse : AppCompatActivity() {
val patientsButton: Button = findViewById(R.id.patientsButton_nurse)
val settingsButton: Button = findViewById(R.id.settingsButton_nurse)
val signOutButton: Button = findViewById(R.id.sighOutButton_nurse)
+ val logsButton: Button = findViewById(R.id.logsButton_nurse)
+
patientsButton.setOnClickListener {
NavigationService(this).onLaunchPatientList()
}
+ logsButton.setOnClickListener {
+ NavigationService(this).onPatientLogs()
+ }
+
// settings button
settingsButton.setOnClickListener {
NavigationService(this).onSettings()
diff --git a/app/src/main/java/deakin/gopher/guardian/view/patient/PatientLogsActivity.kt b/app/src/main/java/deakin/gopher/guardian/view/patient/PatientLogsActivity.kt
new file mode 100644
index 000000000..d34b2b97b
--- /dev/null
+++ b/app/src/main/java/deakin/gopher/guardian/view/patient/PatientLogsActivity.kt
@@ -0,0 +1,210 @@
+package deakin.gopher.guardian.view.patient
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import deakin.gopher.guardian.adapter.PatientLogAdapter
+import deakin.gopher.guardian.databinding.ActivityPatientLogsBinding
+import deakin.gopher.guardian.model.PatientLog
+import deakin.gopher.guardian.services.api.ApiClient
+import kotlinx.coroutines.launch
+import deakin.gopher.guardian.model.login.SessionManager
+import androidx.appcompat.app.AlertDialog
+import android.widget.ArrayAdapter
+import deakin.gopher.guardian.model.Patient
+import deakin.gopher.guardian.view.general.AddPatientLogActivity
+
+
+class PatientLogsActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityPatientLogsBinding
+ private lateinit var adapter: PatientLogAdapter
+
+ private var patients: List = emptyList()
+ private var patientId: String = ""
+ private val api = ApiClient.apiService
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPatientLogsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ setSupportActionBar(binding.toolbar)
+ supportActionBar?.title = "Patient Logs"
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ adapter = PatientLogAdapter(emptyList()) { log ->
+
+ AlertDialog.Builder(this)
+ .setTitle("Delete Log")
+ .setMessage("Are you sure you want to delete this log?")
+ .setPositiveButton("Yes") { _, _ ->
+ deleteLog(log)
+ }
+ .setNegativeButton("Cancel", null)
+ .show()
+ }
+
+ binding.recyclerView.layoutManager = LinearLayoutManager(this)
+ binding.recyclerView.adapter = adapter
+
+ fetchAssignedPatients()
+
+ binding.addLogBtn.setOnClickListener {
+ val intent = Intent(this, AddPatientLogActivity::class.java)
+ intent.putExtra("patientId", patientId)
+ startActivity(intent)
+ }
+ }
+
+ private fun fetchAssignedPatients() {
+
+ lifecycleScope.launch {
+
+ try {
+
+ val response = api.getAssignedPatients(
+ "Bearer ${SessionManager.getToken()}"
+ )
+
+ if (response.isSuccessful) {
+
+ patients = response.body() ?: emptyList()
+
+ val patientNames = patients.map { it.fullname }
+
+ val spinnerAdapter = ArrayAdapter(
+ this@PatientLogsActivity,
+ android.R.layout.simple_spinner_dropdown_item,
+ patientNames
+ )
+
+ binding.patientSpinner.adapter = spinnerAdapter
+
+ binding.patientSpinner.setOnItemSelectedListener(
+ object : android.widget.AdapterView.OnItemSelectedListener {
+
+ override fun onItemSelected(
+ parent: android.widget.AdapterView<*>?,
+ view: android.view.View?,
+ position: Int,
+ id: Long
+ ) {
+
+ patientId = patients[position].id
+ fetchLogs()
+ }
+
+ override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
+ }
+ )
+
+ } else {
+
+ Toast.makeText(
+ this@PatientLogsActivity,
+ "Failed to load patients",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ } catch (e: Exception) {
+
+ Toast.makeText(
+ this@PatientLogsActivity,
+ e.message,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ if (patientId.isNotEmpty()) {
+ fetchLogs()
+ }
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ finish()
+ return true
+ }
+
+ private fun fetchLogs() {
+
+ binding.progressBar.visibility = View.VISIBLE
+ binding.emptyText.visibility = View.GONE
+
+ lifecycleScope.launch {
+
+ try {
+
+ val response = api.getPatientLogs(
+ "Bearer ${SessionManager.getToken()}",
+ patientId
+ )
+
+ binding.progressBar.visibility = View.GONE
+
+ if (response.isSuccessful) {
+
+ val logs = response.body() ?: emptyList()
+
+ adapter.updateData(logs)
+
+ if (logs.isEmpty()) {
+ binding.emptyText.visibility = View.VISIBLE
+ } else {
+ binding.emptyText.visibility = View.GONE
+ }
+
+ } else {
+
+ Toast.makeText(
+ this@PatientLogsActivity,
+ "Failed to load logs",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ } catch (e: Exception) {
+
+ binding.progressBar.visibility = View.GONE
+
+ Toast.makeText(
+ this@PatientLogsActivity,
+ "Something went wrong while loading logs",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
+
+ private fun deleteLog(log: PatientLog) {
+ lifecycleScope.launch {
+ try {
+ val response = api.deletePatientLog(
+ "Bearer ${SessionManager.getToken()}",
+ log._id
+ )
+
+ if (response.isSuccessful) {
+ Toast.makeText(this@PatientLogsActivity, "Log deleted successfully", Toast.LENGTH_SHORT).show()
+ fetchLogs()
+ } else {
+ Toast.makeText(this@PatientLogsActivity, "Delete failed", Toast.LENGTH_SHORT).show()
+ }
+
+ } catch (e: Exception) {
+ Toast.makeText(this@PatientLogsActivity, e.message, Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_patientlogs.xml b/app/src/main/res/drawable/ic_patientlogs.xml
new file mode 100644
index 000000000..8030d9f45
--- /dev/null
+++ b/app/src/main/res/drawable/ic_patientlogs.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_add_patient_log.xml b/app/src/main/res/layout/activity_add_patient_log.xml
new file mode 100644
index 000000000..05d6d4de6
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_patient_log.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_patient_log_screen.xml b/app/src/main/res/layout/activity_add_patient_log_screen.xml
new file mode 100644
index 000000000..3981bb719
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_patient_log_screen.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_homepage4nurse.xml b/app/src/main/res/layout/activity_homepage4nurse.xml
index 2cd321f1e..384539347 100644
--- a/app/src/main/res/layout/activity_homepage4nurse.xml
+++ b/app/src/main/res/layout/activity_homepage4nurse.xml
@@ -77,11 +77,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/medicalDiaganosticsHeaderCardView" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_patient_log.xml b/app/src/main/res/layout/item_patient_log.xml
new file mode 100644
index 000000000..16de6efb2
--- /dev/null
+++ b/app/src/main/res/layout/item_patient_log.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file