This commit is contained in:
@@ -4,26 +4,15 @@
|
|||||||
* @returns Formatted string like "01:23:45 (1h 24m)" or "00:05:23 (5m)"
|
* @returns Formatted string like "01:23:45 (1h 24m)" or "00:05:23 (5m)"
|
||||||
*/
|
*/
|
||||||
export function formatDuration(ms: number): string {
|
export function formatDuration(ms: number): string {
|
||||||
const totalSeconds = Math.floor(ms / 1000);
|
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
||||||
const seconds = totalSeconds % 60;
|
|
||||||
|
|
||||||
const timeStr = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
||||||
|
|
||||||
// Calculate rounded version for easy reading
|
// Calculate rounded version for easy reading
|
||||||
const totalMinutes = Math.round(ms / 1000 / 60);
|
const totalMinutes = Math.round(ms / 1000 / 60);
|
||||||
const roundedHours = Math.floor(totalMinutes / 60);
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
const roundedMinutes = totalMinutes % 60;
|
const minutes = totalMinutes % 60;
|
||||||
|
|
||||||
let roundedStr = '';
|
if (hours > 0) {
|
||||||
if (roundedHours > 0) {
|
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
||||||
roundedStr = roundedMinutes > 0 ? `${roundedHours}h ${roundedMinutes}m` : `${roundedHours}h`;
|
|
||||||
} else {
|
|
||||||
roundedStr = `${roundedMinutes}m`;
|
|
||||||
}
|
}
|
||||||
|
return `${minutes}m`;
|
||||||
return `${timeStr} (${roundedStr})`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +22,7 @@ export function formatDuration(ms: number): string {
|
|||||||
* @returns Formatted duration string or "Running..."
|
* @returns Formatted duration string or "Running..."
|
||||||
*/
|
*/
|
||||||
export function formatTimeRange(start: Date, end: Date | null): string {
|
export function formatTimeRange(start: Date, end: Date | null): string {
|
||||||
if (!end) return 'Running...';
|
if (!end) return "Running...";
|
||||||
const ms = end.getTime() - start.getTime();
|
const ms = end.getTime() - start.getTime();
|
||||||
return formatDuration(ms);
|
return formatDuration(ms);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const allUsers = await db.select().from(users).all();
|
|||||||
<form method="POST" action="/api/admin/settings">
|
<form method="POST" action="/api/admin/settings">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label cursor-pointer">
|
<label class="label cursor-pointer">
|
||||||
<span class="label-text">
|
<span class="label-text flex-1 min-w-0 pr-4">
|
||||||
<div class="font-semibold">Allow New Registrations</div>
|
<div class="font-semibold">Allow New Registrations</div>
|
||||||
<div class="text-sm text-gray-500">When disabled, only existing users can log in</div>
|
<div class="text-sm text-gray-500">When disabled, only existing users can log in</div>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -318,6 +318,20 @@ function getTimeRangeLabel(range: string) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
form {
|
||||||
|
align-items: stretch !important;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const isAdmin = currentUserMember?.member.role === 'owner' || currentUserMember?
|
|||||||
---
|
---
|
||||||
|
|
||||||
<DashboardLayout title="Team - Chronus">
|
<DashboardLayout title="Team - Chronus">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
|
||||||
<h1 class="text-3xl font-bold">Team Members</h1>
|
<h1 class="text-3xl font-bold">Team Members</h1>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
|
|||||||
@@ -200,14 +200,12 @@ const successType = url.searchParams.get('success');
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="label">
|
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6">
|
||||||
<span class="label-text-alt text-base-content/60">
|
<span class="text-xs text-base-content/60 text-center sm:text-left">
|
||||||
Address information appears on invoices and quotes
|
Address information appears on invoices and quotes
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<button type="submit" class="btn btn-primary w-full sm:w-auto">
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
<Icon name="heroicons:check" class="w-5 h-5" />
|
<Icon name="heroicons:check" class="w-5 h-5" />
|
||||||
Save Changes
|
Save Changes
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
type="text"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
placeholder="Search descriptions..."
|
placeholder="Search descriptions..."
|
||||||
class="input input-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
class="input input-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +241,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Client</span>
|
<span class="label-text font-medium">Client</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="client" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors" onchange="this.form.submit()">
|
<select name="client" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full" onchange="this.form.submit()">
|
||||||
<option value="">All Clients</option>
|
<option value="">All Clients</option>
|
||||||
{allClients.map(client => (
|
{allClients.map(client => (
|
||||||
<option value={client.id} selected={filterClient === client.id}>
|
<option value={client.id} selected={filterClient === client.id}>
|
||||||
@@ -255,7 +255,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Category</span>
|
<span class="label-text font-medium">Category</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="category" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors" onchange="this.form.submit()">
|
<select name="category" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full" onchange="this.form.submit()">
|
||||||
<option value="">All Categories</option>
|
<option value="">All Categories</option>
|
||||||
{allCategories.map(category => (
|
{allCategories.map(category => (
|
||||||
<option value={category.id} selected={filterCategory === category.id}>
|
<option value={category.id} selected={filterCategory === category.id}>
|
||||||
@@ -269,7 +269,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Status</span>
|
<span class="label-text font-medium">Status</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="status" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors" onchange="this.form.submit()">
|
<select name="status" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full" onchange="this.form.submit()">
|
||||||
<option value="" selected={filterStatus === ''}>All Entries</option>
|
<option value="" selected={filterStatus === ''}>All Entries</option>
|
||||||
<option value="completed" selected={filterStatus === 'completed'}>Completed</option>
|
<option value="completed" selected={filterStatus === 'completed'}>Completed</option>
|
||||||
<option value="running" selected={filterStatus === 'running'}>Running</option>
|
<option value="running" selected={filterStatus === 'running'}>Running</option>
|
||||||
@@ -280,7 +280,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Entry Type</span>
|
<span class="label-text font-medium">Entry Type</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="type" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors" onchange="this.form.submit()">
|
<select name="type" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full" onchange="this.form.submit()">
|
||||||
<option value="" selected={filterType === ''}>All Types</option>
|
<option value="" selected={filterType === ''}>All Types</option>
|
||||||
<option value="timed" selected={filterType === 'timed'}>Timed</option>
|
<option value="timed" selected={filterType === 'timed'}>Timed</option>
|
||||||
<option value="manual" selected={filterType === 'manual'}>Manual</option>
|
<option value="manual" selected={filterType === 'manual'}>Manual</option>
|
||||||
@@ -291,7 +291,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text font-medium">Sort By</span>
|
<span class="label-text font-medium">Sort By</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="sort" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors" onchange="this.form.submit()">
|
<select name="sort" class="select select-bordered bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors w-full" onchange="this.form.submit()">
|
||||||
<option value="start-desc" selected={sortBy === 'start-desc'}>Newest First</option>
|
<option value="start-desc" selected={sortBy === 'start-desc'}>Newest First</option>
|
||||||
<option value="start-asc" selected={sortBy === 'start-asc'}>Oldest First</option>
|
<option value="start-asc" selected={sortBy === 'start-asc'}>Oldest First</option>
|
||||||
<option value="duration-desc" selected={sortBy === 'duration-desc'}>Longest Duration</option>
|
<option value="duration-desc" selected={sortBy === 'duration-desc'}>Longest Duration</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user