This commit is contained in:
@@ -252,17 +252,15 @@ function getTimeRangeLabel(range: string) {
|
||||
---
|
||||
|
||||
<DashboardLayout title="Reports - Chronus">
|
||||
<h1 class="text-3xl font-bold mb-6">Team Reports</h1>
|
||||
<h1 class="text-2xl font-extrabold tracking-tight mb-6">Team Reports</h1>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card bg-base-200 shadow-xl border border-base-300 mb-6">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-range">
|
||||
Time Range
|
||||
</label>
|
||||
<select id="reports-range" name="range" class="select select-bordered" onchange="this.form.submit()">
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<form method="GET" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Time Range</legend>
|
||||
<select id="reports-range" name="range" class="select w-full" onchange="this.form.submit()">
|
||||
<option value="today" selected={timeRange === 'today'}>Today</option>
|
||||
<option value="week" selected={timeRange === 'week'}>Last 7 Days</option>
|
||||
<option value="month" selected={timeRange === 'month'}>Last 30 Days</option>
|
||||
@@ -271,44 +269,38 @@ function getTimeRangeLabel(range: string) {
|
||||
<option value="last-month" selected={timeRange === 'last-month'}>Last Month</option>
|
||||
<option value="custom" selected={timeRange === 'custom'}>Custom Range</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{timeRange === 'custom' && (
|
||||
<>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-from">
|
||||
From Date
|
||||
</label>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">From Date</legend>
|
||||
<input
|
||||
type="date"
|
||||
id="reports-from"
|
||||
name="from"
|
||||
class="input input-bordered w-full"
|
||||
class="input w-full"
|
||||
value={customFrom || (startDate.getFullYear() + '-' + String(startDate.getMonth() + 1).padStart(2, '0') + '-' + String(startDate.getDate()).padStart(2, '0'))}
|
||||
onchange="this.form.submit()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-to">
|
||||
To Date
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">To Date</legend>
|
||||
<input
|
||||
type="date"
|
||||
id="reports-to"
|
||||
name="to"
|
||||
class="input input-bordered w-full"
|
||||
class="input w-full"
|
||||
value={customTo || (endDate.getFullYear() + '-' + String(endDate.getMonth() + 1).padStart(2, '0') + '-' + String(endDate.getDate()).padStart(2, '0'))}
|
||||
onchange="this.form.submit()"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-member">
|
||||
Team Member
|
||||
</label>
|
||||
<select id="reports-member" name="member" class="select select-bordered" onchange="this.form.submit()">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Team Member</legend>
|
||||
<select id="reports-member" name="member" class="select w-full" onchange="this.form.submit()">
|
||||
<option value="">All Members</option>
|
||||
{teamMembers.map(member => (
|
||||
<option value={member.id} selected={selectedMemberId === member.id}>
|
||||
@@ -316,13 +308,11 @@ function getTimeRangeLabel(range: string) {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-tag">
|
||||
Tag
|
||||
</label>
|
||||
<select id="reports-tag" name="tag" class="select select-bordered" onchange="this.form.submit()">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Tag</legend>
|
||||
<select id="reports-tag" name="tag" class="select w-full" onchange="this.form.submit()">
|
||||
<option value="">All Tags</option>
|
||||
{allTags.map(tag => (
|
||||
<option value={tag.id} selected={selectedTagId === tag.id}>
|
||||
@@ -330,13 +320,11 @@ function getTimeRangeLabel(range: string) {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label font-medium" for="reports-client">
|
||||
Client
|
||||
</label>
|
||||
<select id="reports-client" name="client" class="select select-bordered" onchange="this.form.submit()">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Client</legend>
|
||||
<select id="reports-client" name="client" class="select w-full" onchange="this.form.submit()">
|
||||
<option value="">All Clients</option>
|
||||
{allClients.map(client => (
|
||||
<option value={client.id} selected={selectedClientId === client.id}>
|
||||
@@ -344,76 +332,49 @@ function getTimeRangeLabel(range: string) {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
@media (max-width: 767px) {
|
||||
form {
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.form-control {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
select, input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
||||
<div class="stats shadow border border-base-300">
|
||||
<StatCard
|
||||
title="Total Time"
|
||||
value={formatDuration(totalTime)}
|
||||
description={getTimeRangeLabel(timeRange)}
|
||||
icon="heroicons:clock"
|
||||
color="text-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<StatCard
|
||||
title="Total Entries"
|
||||
value={String(entries.length)}
|
||||
description={getTimeRangeLabel(timeRange)}
|
||||
icon="heroicons:list-bullet"
|
||||
color="text-secondary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<StatCard
|
||||
title="Revenue"
|
||||
value={formatCurrency(revenueStats.total)}
|
||||
description={`${invoiceStats.paid} paid invoices`}
|
||||
icon="heroicons:currency-dollar"
|
||||
color="text-success"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<StatCard
|
||||
title="Active Members"
|
||||
value={String(statsByMember.filter(s => s.entryCount > 0).length)}
|
||||
description={`of ${teamMembers.length} total`}
|
||||
icon="heroicons:user-group"
|
||||
color="text-accent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
|
||||
<StatCard
|
||||
title="Total Time"
|
||||
value={formatDuration(totalTime)}
|
||||
description={getTimeRangeLabel(timeRange)}
|
||||
icon="heroicons:clock"
|
||||
color="text-primary"
|
||||
/>
|
||||
<StatCard
|
||||
title="Total Entries"
|
||||
value={String(entries.length)}
|
||||
description={getTimeRangeLabel(timeRange)}
|
||||
icon="heroicons:list-bullet"
|
||||
color="text-secondary"
|
||||
/>
|
||||
<StatCard
|
||||
title="Revenue"
|
||||
value={formatCurrency(revenueStats.total)}
|
||||
description={`${invoiceStats.paid} paid invoices`}
|
||||
icon="heroicons:currency-dollar"
|
||||
color="text-success"
|
||||
/>
|
||||
<StatCard
|
||||
title="Active Members"
|
||||
value={String(statsByMember.filter(s => s.entryCount > 0).length)}
|
||||
description={`of ${teamMembers.length} total`}
|
||||
icon="heroicons:user-group"
|
||||
color="text-accent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Invoice & Quote Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:document-text" class="w-6 h-6" />
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:document-text" class="w-4 h-4" />
|
||||
Invoices Overview
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
@@ -446,10 +407,10 @@ function getTimeRangeLabel(range: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:clipboard-document-list" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:clipboard-document-list" class="w-4 h-4" />
|
||||
Quotes Overview
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
@@ -487,14 +448,14 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
<!-- Revenue by Client - Only show if there's revenue data and no client filter -->
|
||||
{!selectedClientId && revenueByClient.length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:banknotes" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:banknotes" class="w-4 h-4" />
|
||||
Revenue by Client
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
@@ -507,11 +468,11 @@ function getTimeRangeLabel(range: string) {
|
||||
{revenueByClient.slice(0, 10).map(stat => (
|
||||
<tr>
|
||||
<td>
|
||||
<div class="font-bold">{stat.client.name}</div>
|
||||
<div class="font-medium">{stat.client.name}</div>
|
||||
</td>
|
||||
<td class="font-mono font-bold text-success">{formatCurrency(stat.revenue)}</td>
|
||||
<td class="font-mono font-semibold text-success text-sm">{formatCurrency(stat.revenue)}</td>
|
||||
<td>{stat.invoiceCount}</td>
|
||||
<td class="font-mono">
|
||||
<td class="font-mono text-sm">
|
||||
{stat.invoiceCount > 0 ? formatCurrency(stat.revenue / stat.invoiceCount) : formatCurrency(0)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -526,13 +487,13 @@ function getTimeRangeLabel(range: string) {
|
||||
{/* Charts Section - Only show if there's data */}
|
||||
{totalTime > 0 && (
|
||||
<>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||
{/* Tag Distribution Chart - Only show when no tag filter */}
|
||||
{!selectedTagId && statsByTag.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:chart-pie" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:chart-pie" class="w-4 h-4" />
|
||||
Tag Distribution
|
||||
</h2>
|
||||
<div class="h-64 w-full">
|
||||
@@ -551,10 +512,10 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
{/* Client Distribution Chart - Only show when no client filter */}
|
||||
{!selectedClientId && statsByClient.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:chart-bar" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:chart-bar" class="w-4 h-4" />
|
||||
Time by Client
|
||||
</h2>
|
||||
<div class="h-64 w-full">
|
||||
@@ -573,10 +534,10 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
{/* Team Member Chart - Only show when no member filter */}
|
||||
{!selectedMemberId && statsByMember.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:users" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:users" class="w-4 h-4" />
|
||||
Time by Team Member
|
||||
</h2>
|
||||
<div class="h-64 w-full">
|
||||
@@ -596,14 +557,14 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
{/* Stats by Member - Only show if there's data and no member filter */}
|
||||
{!selectedMemberId && statsByMember.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:users" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:users" class="w-4 h-4" />
|
||||
By Team Member
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Member</th>
|
||||
@@ -617,13 +578,13 @@ function getTimeRangeLabel(range: string) {
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<div class="font-bold">{stat.member.name}</div>
|
||||
<div class="text-sm opacity-50">{stat.member.email}</div>
|
||||
<div class="font-medium">{stat.member.name}</div>
|
||||
<div class="text-xs text-base-content/40">{stat.member.email}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="font-mono">{formatDuration(stat.totalTime)}</td>
|
||||
<td class="font-mono text-sm">{formatDuration(stat.totalTime)}</td>
|
||||
<td>{stat.entryCount}</td>
|
||||
<td class="font-mono">
|
||||
<td class="font-mono text-sm">
|
||||
{stat.entryCount > 0 ? formatDuration(stat.totalTime / stat.entryCount) : '00:00:00 (0m)'}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -637,14 +598,14 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
{/* Stats by Tag - Only show if there's data and no tag filter */}
|
||||
{!selectedTagId && statsByTag.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:tag" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:tag" class="w-4 h-4" />
|
||||
By Tag
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
@@ -659,21 +620,21 @@ function getTimeRangeLabel(range: string) {
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
{stat.tag.color && (
|
||||
<span class="w-4 h-4 rounded-full" style={`background-color: ${stat.tag.color}`}></span>
|
||||
<span class="w-3 h-3 rounded-full" style={`background-color: ${stat.tag.color}`}></span>
|
||||
)}
|
||||
<span>{stat.tag.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="font-mono">{formatDuration(stat.totalTime)}</td>
|
||||
<td class="font-mono text-sm">{formatDuration(stat.totalTime)}</td>
|
||||
<td>{stat.entryCount}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<progress
|
||||
class="progress progress-primary w-20"
|
||||
class="progress progress-primary w-16"
|
||||
value={stat.totalTime}
|
||||
max={totalTime}
|
||||
></progress>
|
||||
<span class="text-sm">
|
||||
<span class="text-xs">
|
||||
{totalTime > 0 ? Math.round((stat.totalTime / totalTime) * 100) : 0}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -689,14 +650,14 @@ function getTimeRangeLabel(range: string) {
|
||||
|
||||
{/* Stats by Client - Only show if there's data and no client filter */}
|
||||
{!selectedClientId && statsByClient.filter(s => s.totalTime > 0).length > 0 && (
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Icon name="heroicons:building-office" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100 mb-6">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2 mb-3">
|
||||
<Icon name="heroicons:building-office" class="w-4 h-4" />
|
||||
By Client
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client</th>
|
||||
@@ -709,16 +670,16 @@ function getTimeRangeLabel(range: string) {
|
||||
{statsByClient.filter(s => s.totalTime > 0).map(stat => (
|
||||
<tr>
|
||||
<td>{stat.client.name}</td>
|
||||
<td class="font-mono">{formatDuration(stat.totalTime)}</td>
|
||||
<td class="font-mono text-sm">{formatDuration(stat.totalTime)}</td>
|
||||
<td>{stat.entryCount}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<progress
|
||||
class="progress progress-secondary w-20"
|
||||
class="progress progress-secondary w-16"
|
||||
value={stat.totalTime}
|
||||
max={totalTime}
|
||||
></progress>
|
||||
<span class="text-sm">
|
||||
<span class="text-xs">
|
||||
{totalTime > 0 ? Math.round((stat.totalTime / totalTime) * 100) : 0}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -733,23 +694,23 @@ function getTimeRangeLabel(range: string) {
|
||||
)}
|
||||
|
||||
{/* Detailed Entries */}
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:document-text" class="w-6 h-6" />
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h2 class="text-sm font-semibold flex items-center gap-2">
|
||||
<Icon name="heroicons:document-text" class="w-4 h-4" />
|
||||
Detailed Entries ({entries.length})
|
||||
</h2>
|
||||
{entries.length > 0 && (
|
||||
<a href={`/api/reports/export${url.search}`} class="btn btn-sm btn-outline" target="_blank">
|
||||
<Icon name="heroicons:arrow-down-tray" class="w-4 h-4" />
|
||||
<a href={`/api/reports/export${url.search}`} class="btn btn-xs btn-ghost" target="_blank">
|
||||
<Icon name="heroicons:arrow-down-tray" class="w-3.5 h-3.5" />
|
||||
Export CSV
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{entries.length > 0 ? (
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
@@ -765,7 +726,7 @@ function getTimeRangeLabel(range: string) {
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">
|
||||
{e.entry.startTime.toLocaleDateString()}<br/>
|
||||
<span class="text-xs opacity-50">
|
||||
<span class="text-xs text-base-content/40">
|
||||
{e.entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
|
||||
</span>
|
||||
</td>
|
||||
@@ -773,18 +734,18 @@ function getTimeRangeLabel(range: string) {
|
||||
<td>{e.client.name}</td>
|
||||
<td>
|
||||
{e.tag ? (
|
||||
<div class="badge badge-sm badge-outline flex items-center gap-1">
|
||||
<div class="badge badge-xs badge-outline flex items-center gap-1">
|
||||
{e.tag.color && (
|
||||
<span class="w-2 h-2 rounded-full" style={`background-color: ${e.tag.color}`}></span>
|
||||
)}
|
||||
<span>{e.tag.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span class="opacity-50">-</span>
|
||||
<span class="text-base-content/30">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{e.entry.description || '-'}</td>
|
||||
<td class="font-mono">
|
||||
<td class="text-base-content/60">{e.entry.description || '-'}</td>
|
||||
<td class="font-mono text-sm">
|
||||
{e.entry.endTime
|
||||
? formatDuration(e.entry.endTime.getTime() - e.entry.startTime.getTime())
|
||||
: 'Running...'
|
||||
@@ -796,12 +757,12 @@ function getTimeRangeLabel(range: string) {
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<Icon name="heroicons:inbox" class="w-16 h-16 text-base-content/20 mb-4" />
|
||||
<h3 class="text-lg font-semibold mb-2">No time entries found</h3>
|
||||
<p class="text-base-content/60 mb-4">Try adjusting your filters or select a different time range.</p>
|
||||
<a href="/dashboard/tracker" class="btn btn-primary">
|
||||
<Icon name="heroicons:play" class="w-5 h-5" />
|
||||
<div class="flex flex-col items-center justify-center py-10 text-center">
|
||||
<Icon name="heroicons:inbox" class="w-12 h-12 text-base-content/15 mb-3" />
|
||||
<h3 class="text-base font-semibold mb-1">No time entries found</h3>
|
||||
<p class="text-base-content/50 text-sm mb-4">Try adjusting your filters or select a different time range.</p>
|
||||
<a href="/dashboard/tracker" class="btn btn-primary btn-sm">
|
||||
<Icon name="heroicons:play" class="w-4 h-4" />
|
||||
Start Tracking Time
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user