232 lines
11 KiB
C#
232 lines
11 KiB
C#
|
|
using Contime.model;
|
|||
|
|
using Microsoft.Data.Sqlite;
|
|||
|
|
using System.Globalization;
|
|||
|
|
|
|||
|
|
namespace Contime.data {
|
|||
|
|
public class DataAccess {
|
|||
|
|
private readonly string _connectionString;
|
|||
|
|
|
|||
|
|
public DataAccess(string dbFileName = "contime.db") {
|
|||
|
|
_connectionString = $"Data Source={dbFileName}";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void InitializeDatabase() {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = @"
|
|||
|
|
CREATE TABLE IF NOT EXISTS workdays (
|
|||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|||
|
|
workday_date TEXT NOT NULL UNIQUE
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|||
|
|
description TEXT NOT NULL,
|
|||
|
|
created_date TEXT NOT NULL,
|
|||
|
|
completed_date TEXT,
|
|||
|
|
status TEXT NOT NULL CHECK(status IN ('open', 'closed'))
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS task_times (
|
|||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|||
|
|
task_id INTEGER NOT NULL,
|
|||
|
|
workday_id INTEGER NOT NULL,
|
|||
|
|
start_time TEXT NOT NULL,
|
|||
|
|
end_time TEXT,
|
|||
|
|
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (workday_id) REFERENCES workdays(id) ON DELETE CASCADE
|
|||
|
|
);
|
|||
|
|
";
|
|||
|
|
command.ExecuteNonQuery();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public long GetOrCreateWorkday(DateTime date) {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var selectCmd = connection.CreateCommand();
|
|||
|
|
selectCmd.CommandText = "SELECT id FROM workdays WHERE workday_date = $date";
|
|||
|
|
selectCmd.Parameters.AddWithValue("$date", date.ToString("yyyy-MM-dd"));
|
|||
|
|
var result = selectCmd.ExecuteScalar();
|
|||
|
|
|
|||
|
|
if (result != null) {
|
|||
|
|
return (long)result;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
var insertCmd = connection.CreateCommand();
|
|||
|
|
insertCmd.CommandText = "INSERT INTO workdays (workday_date) VALUES ($date); SELECT last_insert_rowid();";
|
|||
|
|
insertCmd.Parameters.AddWithValue("$date", date.ToString("yyyy-MM-dd"));
|
|||
|
|
return (long)insertCmd.ExecuteScalar();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public List<TaskItem> GetAllTasks() {
|
|||
|
|
var tasks = new List<TaskItem>();
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = @"
|
|||
|
|
SELECT t.id, t.description, t.status,
|
|||
|
|
COALESCE(SUM(CAST((julianday(tt.end_time) - julianday(tt.start_time)) * 86400 AS INTEGER)), 0)
|
|||
|
|
FROM tasks t
|
|||
|
|
LEFT JOIN task_times tt ON t.id = tt.task_id AND tt.end_time IS NOT NULL
|
|||
|
|
GROUP BY t.id, t.description, t.status
|
|||
|
|
ORDER BY t.created_date DESC";
|
|||
|
|
|
|||
|
|
using (var reader = command.ExecuteReader()) {
|
|||
|
|
while (reader.Read()) {
|
|||
|
|
var taskItem = new TaskItem(
|
|||
|
|
id: reader.GetInt64(0),
|
|||
|
|
name: reader.GetString(1),
|
|||
|
|
status: reader.GetString(2),
|
|||
|
|
elapsedTime: TimeSpan.FromSeconds(reader.GetInt32(3))
|
|||
|
|
);
|
|||
|
|
tasks.Add(taskItem);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return tasks;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void AddTask(string description) {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = "INSERT INTO tasks (description, created_date, status) VALUES ($desc, $date, 'open')";
|
|||
|
|
command.Parameters.AddWithValue("$desc", description);
|
|||
|
|
command.Parameters.AddWithValue("$date", DateTime.UtcNow.ToString("o"));
|
|||
|
|
command.ExecuteNonQuery();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void UpdateTaskStatus(long taskId, string status, bool isCompletion) {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = isCompletion
|
|||
|
|
? "UPDATE tasks SET status = $status, completed_date = $date WHERE id = $id"
|
|||
|
|
: "UPDATE tasks SET status = $status, completed_date = NULL WHERE id = $id";
|
|||
|
|
|
|||
|
|
command.Parameters.AddWithValue("$status", status);
|
|||
|
|
command.Parameters.AddWithValue("$id", taskId);
|
|||
|
|
if (isCompletion) command.Parameters.AddWithValue("$date", DateTime.UtcNow.ToString("o"));
|
|||
|
|
command.ExecuteNonQuery();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public long StartTaskTime(long taskId, long workdayId) {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = "INSERT INTO task_times (task_id, workday_id, start_time) VALUES ($taskId, $workdayId, $startTime); SELECT last_insert_rowid();";
|
|||
|
|
command.Parameters.AddWithValue("$taskId", taskId);
|
|||
|
|
command.Parameters.AddWithValue("$workdayId", workdayId);
|
|||
|
|
command.Parameters.AddWithValue("$startTime", DateTime.UtcNow.ToString("o"));
|
|||
|
|
return (long)command.ExecuteScalar();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void EndTaskTime(long taskTimeId) {
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = "UPDATE task_times SET end_time = $endTime WHERE id = $id";
|
|||
|
|
command.Parameters.AddWithValue("$endTime", DateTime.UtcNow.ToString("o"));
|
|||
|
|
command.Parameters.AddWithValue("$id", taskTimeId);
|
|||
|
|
command.ExecuteNonQuery();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public TimeSpan GetTotalTimeForDate(DateTime date) {
|
|||
|
|
long totalSeconds = 0;
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = @"
|
|||
|
|
SELECT start_time, end_time FROM task_times
|
|||
|
|
WHERE end_time IS NOT NULL";
|
|||
|
|
|
|||
|
|
using (var reader = command.ExecuteReader()) {
|
|||
|
|
while (reader.Read()) {
|
|||
|
|
var start = DateTime.Parse(reader.GetString(0), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
var end = DateTime.Parse(reader.GetString(1), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
|
|||
|
|
if (start.Date == date.Date) {
|
|||
|
|
var effectiveEnd = (end.Date > start.Date) ? start.Date.AddDays(1) : end;
|
|||
|
|
totalSeconds += (long)(effectiveEnd - start).TotalSeconds;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return TimeSpan.FromSeconds(totalSeconds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public TimeSpan GetTotalTimeForCurrentWeek() {
|
|||
|
|
long totalSeconds = 0;
|
|||
|
|
var today = DateTime.Today;
|
|||
|
|
// Sunday is 0, so we adjust to make Monday the start of the week (1)
|
|||
|
|
int diff = (7 + (int)today.DayOfWeek - (int)DayOfWeek.Monday) % 7;
|
|||
|
|
var startOfWeek = today.AddDays(-1 * diff).Date;
|
|||
|
|
var endOfWeek = startOfWeek.AddDays(6);
|
|||
|
|
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = @"
|
|||
|
|
SELECT start_time, end_time FROM task_times
|
|||
|
|
WHERE end_time IS NOT NULL";
|
|||
|
|
|
|||
|
|
using (var reader = command.ExecuteReader()) {
|
|||
|
|
while (reader.Read()) {
|
|||
|
|
var start = DateTime.Parse(reader.GetString(0), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
var end = DateTime.Parse(reader.GetString(1), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
|
|||
|
|
if (start.Date >= startOfWeek && start.Date <= endOfWeek) {
|
|||
|
|
totalSeconds += (long)(end - start).TotalSeconds;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return TimeSpan.FromSeconds(totalSeconds);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public List<(DateTime Date, string TaskName, TimeSpan Duration)> GetAllTimeEntriesForExport() {
|
|||
|
|
var entries = new List<(DateTime Date, string TaskName, TimeSpan Duration)>();
|
|||
|
|
using (var connection = new SqliteConnection(_connectionString)) {
|
|||
|
|
connection.Open();
|
|||
|
|
var command = connection.CreateCommand();
|
|||
|
|
command.CommandText = @"
|
|||
|
|
SELECT t.description, tt.start_time, tt.end_time
|
|||
|
|
FROM task_times tt
|
|||
|
|
JOIN tasks t ON tt.task_id = t.id
|
|||
|
|
WHERE tt.end_time IS NOT NULL
|
|||
|
|
ORDER BY tt.start_time";
|
|||
|
|
|
|||
|
|
using (var reader = command.ExecuteReader()) {
|
|||
|
|
while (reader.Read()) {
|
|||
|
|
var taskName = reader.GetString(0);
|
|||
|
|
var startTime = DateTime.Parse(reader.GetString(1), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
var endTime = DateTime.Parse(reader.GetString(2), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind).ToLocalTime();
|
|||
|
|
|
|||
|
|
var currentDate = startTime.Date;
|
|||
|
|
while (currentDate <= endTime.Date) {
|
|||
|
|
var startOfThisDay = (currentDate == startTime.Date) ? startTime : currentDate;
|
|||
|
|
var endOfThisDay = (currentDate == endTime.Date) ? endTime : currentDate.AddDays(1).AddTicks(-1);
|
|||
|
|
|
|||
|
|
var durationThisDay = endOfThisDay - startOfThisDay;
|
|||
|
|
|
|||
|
|
entries.Add((currentDate, taskName, durationThisDay));
|
|||
|
|
|
|||
|
|
currentDate = currentDate.AddDays(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return entries;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|