ViewModel for dashboard

This commit is contained in:
2025-12-25 13:45:27 +01:00
parent 167d18124a
commit 3a37eac107
6 changed files with 74 additions and 37 deletions

View File

@@ -15,9 +15,8 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -25,39 +24,38 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import net.tinsae.clocked.components.ActivityList
import net.tinsae.clocked.service.deleteLog
import net.tinsae.clocked.service.editLog
import net.tinsae.clocked.service.getRecentLogs
import net.tinsae.clocked.ui.theme.ClockedTheme
import net.tinsae.clocked.ui.theme.cyan
import net.tinsae.clocked.ui.theme.green
import net.tinsae.clocked.ui.theme.red
@Composable
fun DashboardScreen(modifier: Modifier = Modifier) {
fun DashboardScreen(
modifier: Modifier = Modifier,
viewModel: DashboardViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsState()
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp,0.dp)
.padding(16.dp, 0.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
SummaryCard(title = "Overtime", value = "+12h 30m", color = green, modifier = Modifier.weight(1f))
SummaryCard(title = "Overtime", value = uiState.overtime, color = green, modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.width(16.dp))
SummaryCard(title = "Time Off", value = "6h 00m", color = red, modifier = Modifier.weight(1f))
SummaryCard(title = "Time Off", value = uiState.timeOff, color = red, modifier = Modifier.weight(1f))
}
// ADDED: Manual spacer
Spacer(modifier = Modifier.height(16.dp))
NetBalanceCard(value = "+6h 30m")
NetBalanceCard(value = uiState.netBalance)
// ADDED: Manual spacer
Spacer(modifier = Modifier.height(16.dp))
Row(
@@ -69,13 +67,10 @@ fun DashboardScreen(modifier: Modifier = Modifier) {
ActionButton(text = " Time Off", modifier = Modifier.weight(1f))
}
// ADDED: Manual spacer
Spacer(modifier = Modifier.height(16.dp))
// This section now works as expected
val recentLogs by remember { mutableStateOf(getRecentLogs()) }
ActivityList(
items = recentLogs,
items = uiState.recentActivities,
modifier = Modifier.weight(1f)
)
}
@@ -131,6 +126,6 @@ fun ActionButton(text: String, modifier: Modifier = Modifier) {
@Composable
fun DashboardScreenPreview() {
ClockedTheme {
DashboardScreen()
DashboardScreen(viewModel = DashboardViewModel())
}
}

View File

@@ -0,0 +1,43 @@
package net.tinsae.clocked.dashboard
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import net.tinsae.clocked.data.Log
import net.tinsae.clocked.service.getRecentLogs
import net.tinsae.clocked.service.getTotalOvertimeDuration
import net.tinsae.clocked.service.getTotalTimeOffDuration
import net.tinsae.clocked.util.Util.formatDuration
data class DashboardUiState(
val overtime: String = "0m",
val timeOff: String = "0m",
val netBalance: String = "0m",
val recentActivities: List<Log> = emptyList()
)
class DashboardViewModel : ViewModel() {
private val _uiState = MutableStateFlow(DashboardUiState())
val uiState: StateFlow<DashboardUiState> = _uiState
init {
loadDashboardData()
}
private fun loadDashboardData() {
val recentLogs = getRecentLogs()
val overtimeDuration = getTotalOvertimeDuration()
val timeOffDuration = getTotalTimeOffDuration()
// timeOffDuration is negative, so we add it to get the difference
val netBalanceDuration = overtimeDuration.plus(timeOffDuration)
_uiState.value = DashboardUiState(
overtime = formatDuration(overtimeDuration),
timeOff = formatDuration(timeOffDuration),
netBalance = formatDuration(netBalanceDuration),
recentActivities = recentLogs
)
}
}

View File

@@ -6,15 +6,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -35,22 +32,7 @@ import net.tinsae.clocked.service.getAllLogs
import net.tinsae.clocked.service.getOvertimeLogs
import net.tinsae.clocked.service.getTimeOffLogs
import net.tinsae.clocked.ui.theme.ClockedTheme
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
// Helper to format the Instant into a readable string like "Tue 18"
fun formatTimestampToDayAndDate(instant: Instant): String {
val formatter = DateTimeFormatter.ofPattern("E d").withZone(ZoneId.systemDefault())
return formatter.format(instant)
}
// Helper to format the Instant into a month and year like "March 2025"
fun formatTimestampToMonthYear(instant: Instant): String {
val formatter = DateTimeFormatter.ofPattern("MMMM yyyy").withZone(ZoneId.systemDefault())
return formatter.format(instant)
}
import net.tinsae.clocked.util.Util.formatTimestampToMonthYear
@OptIn(ExperimentalFoundationApi::class)
@Composable

View File

@@ -69,6 +69,20 @@ fun getRecentLogs(): List<Log> {
return logs.take(7)
}
fun getTotalOvertimeDuration(): Duration {
return logs
.filter { it.type == EntryType.OVERTIME }
.map { it.duration }
.fold(Duration.ZERO, Duration::plus)
}
fun getTotalTimeOffDuration(): Duration {
return logs
.filter { it.type == EntryType.TIME_OFF }
.map { it.duration }
.fold(Duration.ZERO, Duration::plus)
}
fun deleteLog(log: Log): Boolean {
logs.filter { it.id != log.id }