missing api features
This commit is contained in:
@@ -7,7 +7,6 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AuthResponse(
|
data class AuthResponse(
|
||||||
val token: String
|
val token: String
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
||||||
|
|
||||||
@@ -39,21 +42,38 @@ class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
|||||||
private fun startLocalTimer() {
|
private fun startLocalTimer() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
state.value?.entry?.startTime?.let { isoTime ->
|
val currentEntry = state.value?.entry
|
||||||
|
if (state.value?.isTracking == true && currentEntry != null) {
|
||||||
try {
|
try {
|
||||||
// Assuming ISO format like "2023-10-27T10:00:00Z"
|
val startTimeStr = currentEntry.startTime
|
||||||
val start = Instant.parse(isoTime)
|
|
||||||
val now = Instant.now()
|
// Robust Parsing: Try Instant first, fallback to LocalDateTime assuming System Default Zone
|
||||||
val seconds = Duration.between(start, now).seconds
|
val startInstant = try {
|
||||||
if (seconds >= 0) {
|
Instant.parse(startTimeStr)
|
||||||
val h = seconds / 3600
|
} catch (e: Exception) {
|
||||||
val m = (seconds % 3600) / 60
|
// CHANGE: Use systemDefault() instead of UTC.
|
||||||
val s = seconds % 60
|
// If server sends "15:51" and it is 15:51 on your clock, this matches the timeline correctly.
|
||||||
elapsedTime.value = String.format("%02d:%02d:%02d", h, m, s)
|
LocalDateTime.parse(startTimeStr)
|
||||||
|
.atZone(java.time.ZoneId.systemDefault())
|
||||||
|
.toInstant()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = Instant.now()
|
||||||
|
var diffSeconds = Duration.between(startInstant, now).seconds
|
||||||
|
|
||||||
|
// Prevent negative time display due to small clock skews
|
||||||
|
if (diffSeconds < 0) diffSeconds = 0
|
||||||
|
|
||||||
|
val h = diffSeconds / 3600
|
||||||
|
val m = (diffSeconds % 3600) / 60
|
||||||
|
val s = diffSeconds % 60
|
||||||
|
elapsedTime.value = String.format("%02d:%02d:%02d", h, m, s)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
elapsedTime.value = "--:--"
|
elapsedTime.value = "--:--"
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
elapsedTime.value = "00:00:00"
|
||||||
}
|
}
|
||||||
delay(1000)
|
delay(1000)
|
||||||
}
|
}
|
||||||
@@ -62,10 +82,10 @@ class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
|||||||
|
|
||||||
fun startActivity(activityId: String) {
|
fun startActivity(activityId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// Optimistic UI Update possible here
|
|
||||||
try {
|
try {
|
||||||
api.startActivity(activityId)
|
api.startActivity(activityId)
|
||||||
// Polling will update the full state shortly
|
// Refresh immediately instead of waiting for the next poll
|
||||||
|
state.value = api.getStatus()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -76,7 +96,7 @@ class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
api.stopActivity()
|
api.stopActivity()
|
||||||
// Explicitly refresh state immediately for better UX
|
// Refresh immediately
|
||||||
state.value = api.getStatus()
|
state.value = api.getStatus()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -84,4 +104,3 @@ class DashboardViewModel(private val api: ApiService) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
112
app.py
112
app.py
@@ -257,7 +257,7 @@ def start_timer_bg(activity_id):
|
|||||||
'time_entry_id': new_entry_id,
|
'time_entry_id': new_entry_id,
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
'source': 'auto', # Mark as auto-generated
|
'source': 'auto',
|
||||||
'created_at': datetime.now(),
|
'created_at': datetime.now(),
|
||||||
'comments': []
|
'comments': []
|
||||||
})
|
})
|
||||||
@@ -886,6 +886,116 @@ def api_status(user_id):
|
|||||||
} for a in activities]
|
} for a in activities]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# --- API Timer Controls (Token Based) ---
|
||||||
|
|
||||||
|
@app.route('/api/timer/start/<activity_id>', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def api_start_timer(user_id, activity_id):
|
||||||
|
# Stop ANY running timer
|
||||||
|
db.time_entries.update_many(
|
||||||
|
{'user_id': user_id, 'end_time': None},
|
||||||
|
{'$set': {'end_time': datetime.now()}}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start new
|
||||||
|
start_time = datetime.now()
|
||||||
|
new_entry_id = db.time_entries.insert_one({
|
||||||
|
'user_id': user_id,
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'start_time': start_time,
|
||||||
|
'end_time': None,
|
||||||
|
'note': '',
|
||||||
|
'subcategory': ''
|
||||||
|
}).inserted_id
|
||||||
|
|
||||||
|
# Auto-add templates
|
||||||
|
templates = db.tasks.find({
|
||||||
|
'user_id': user_id,
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'is_template': True
|
||||||
|
})
|
||||||
|
|
||||||
|
for t in templates:
|
||||||
|
db.tasks.insert_one({
|
||||||
|
'user_id': user_id,
|
||||||
|
'name': t['name'],
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'time_entry_id': new_entry_id,
|
||||||
|
'status': 'open',
|
||||||
|
'is_template': False,
|
||||||
|
'source': 'auto',
|
||||||
|
'created_at': datetime.now(),
|
||||||
|
'comments': []
|
||||||
|
})
|
||||||
|
|
||||||
|
activity = db.activities.find_one({'_id': ObjectId(activity_id)})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'entry_id': str(new_entry_id),
|
||||||
|
'start_time': start_time.isoformat(),
|
||||||
|
'activity_name': activity['name'],
|
||||||
|
'activity_color': activity.get('color', '#3498db')
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/api/timer/stop', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def api_stop_timer(user_id):
|
||||||
|
result = db.time_entries.update_many(
|
||||||
|
{'user_id': user_id, 'end_time': None},
|
||||||
|
{'$set': {'end_time': datetime.now()}}
|
||||||
|
)
|
||||||
|
return jsonify({'status': 'stopped', 'modified': result.modified_count})
|
||||||
|
|
||||||
|
@app.route('/api/timer/update', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def api_update_active_entry(user_id):
|
||||||
|
data = request.get_json()
|
||||||
|
fields = {}
|
||||||
|
if 'note' in data: fields['note'] = data['note']
|
||||||
|
if 'subcategory' in data: fields['subcategory'] = data['subcategory']
|
||||||
|
|
||||||
|
if not fields:
|
||||||
|
return jsonify({'message': 'No fields to update'}), 400
|
||||||
|
|
||||||
|
db.time_entries.update_one(
|
||||||
|
{'user_id': user_id, 'end_time': None},
|
||||||
|
{'$set': fields}
|
||||||
|
)
|
||||||
|
return jsonify({'status': 'updated'})
|
||||||
|
|
||||||
|
@app.route('/api/tasks/toggle', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def api_toggle_task(user_id):
|
||||||
|
data = request.get_json()
|
||||||
|
task_id = data.get('task_id')
|
||||||
|
is_checked = data.get('is_checked')
|
||||||
|
|
||||||
|
if not task_id: return jsonify({'message': 'task_id required'}), 400
|
||||||
|
|
||||||
|
status = 'completed' if is_checked else 'open'
|
||||||
|
completed_at = datetime.now() if is_checked else None
|
||||||
|
|
||||||
|
update_doc = {
|
||||||
|
'status': status,
|
||||||
|
'completed_at': completed_at
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: Link to active timer if checking off
|
||||||
|
if is_checked:
|
||||||
|
current_entry = db.time_entries.find_one({
|
||||||
|
'user_id': user_id,
|
||||||
|
'end_time': None
|
||||||
|
})
|
||||||
|
if current_entry:
|
||||||
|
update_doc['time_entry_id'] = current_entry['_id']
|
||||||
|
|
||||||
|
db.tasks.update_one(
|
||||||
|
{'_id': ObjectId(task_id), 'user_id': user_id},
|
||||||
|
{'$set': update_doc}
|
||||||
|
)
|
||||||
|
return jsonify({'status': 'success'})
|
||||||
|
|
||||||
# --- Activity Management API ---
|
# --- Activity Management API ---
|
||||||
|
|
||||||
@app.route('/api/activities/create', methods=['POST'])
|
@app.route('/api/activities/create', methods=['POST'])
|
||||||
|
|||||||
Reference in New Issue
Block a user