Updated
This commit is contained in:
@@ -4,10 +4,14 @@ import { Icon } from 'astro-icon/components';
|
||||
import { db } from '../../db';
|
||||
import { organizations, members, timeEntries, clients, categories } from '../../db/schema';
|
||||
import { eq, desc, and, isNull, gte, sql } from 'drizzle-orm';
|
||||
import { formatDuration } from '../../lib/formatTime';
|
||||
|
||||
const user = Astro.locals.user;
|
||||
if (!user) return Astro.redirect('/login');
|
||||
|
||||
// Get current team from cookie or first membership
|
||||
const currentTeamId = Astro.cookies.get('currentTeamId')?.value;
|
||||
|
||||
const userOrgs = await db.select({
|
||||
id: organizations.id,
|
||||
name: organizations.name,
|
||||
@@ -19,7 +23,11 @@ const userOrgs = await db.select({
|
||||
.where(eq(members.userId, user.id))
|
||||
.all();
|
||||
|
||||
const firstOrg = userOrgs[0];
|
||||
// Use current team or fallback to first
|
||||
const currentOrg = currentTeamId
|
||||
? userOrgs.find(o => o.organizationId === currentTeamId) || userOrgs[0]
|
||||
: userOrgs[0];
|
||||
|
||||
let stats = {
|
||||
totalTimeThisWeek: 0,
|
||||
totalTimeThisMonth: 0,
|
||||
@@ -28,7 +36,7 @@ let stats = {
|
||||
recentEntries: [] as any[],
|
||||
};
|
||||
|
||||
if (firstOrg) {
|
||||
if (currentOrg) {
|
||||
const now = new Date();
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
@@ -36,7 +44,7 @@ if (firstOrg) {
|
||||
const weekEntries = await db.select()
|
||||
.from(timeEntries)
|
||||
.where(and(
|
||||
eq(timeEntries.organizationId, firstOrg.organizationId),
|
||||
eq(timeEntries.organizationId, currentOrg.organizationId),
|
||||
gte(timeEntries.startTime, weekAgo)
|
||||
))
|
||||
.all();
|
||||
@@ -51,7 +59,7 @@ if (firstOrg) {
|
||||
const monthEntries = await db.select()
|
||||
.from(timeEntries)
|
||||
.where(and(
|
||||
eq(timeEntries.organizationId, firstOrg.organizationId),
|
||||
eq(timeEntries.organizationId, currentOrg.organizationId),
|
||||
gte(timeEntries.startTime, monthAgo)
|
||||
))
|
||||
.all();
|
||||
@@ -66,7 +74,7 @@ if (firstOrg) {
|
||||
const activeCount = await db.select()
|
||||
.from(timeEntries)
|
||||
.where(and(
|
||||
eq(timeEntries.organizationId, firstOrg.organizationId),
|
||||
eq(timeEntries.organizationId, currentOrg.organizationId),
|
||||
isNull(timeEntries.endTime)
|
||||
))
|
||||
.all();
|
||||
@@ -75,7 +83,7 @@ if (firstOrg) {
|
||||
|
||||
const clientCount = await db.select()
|
||||
.from(clients)
|
||||
.where(eq(clients.organizationId, firstOrg.organizationId))
|
||||
.where(eq(clients.organizationId, currentOrg.organizationId))
|
||||
.all();
|
||||
|
||||
stats.totalClients = clientCount.length;
|
||||
@@ -88,144 +96,137 @@ if (firstOrg) {
|
||||
.from(timeEntries)
|
||||
.innerJoin(clients, eq(timeEntries.clientId, clients.id))
|
||||
.innerJoin(categories, eq(timeEntries.categoryId, categories.id))
|
||||
.where(eq(timeEntries.userId, user.id))
|
||||
.where(eq(timeEntries.organizationId, currentOrg.organizationId))
|
||||
.orderBy(desc(timeEntries.startTime))
|
||||
.limit(5)
|
||||
.all();
|
||||
}
|
||||
|
||||
function formatDuration(ms: number) {
|
||||
const totalMinutes = Math.round(ms / 1000 / 60);
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
const hasMembership = userOrgs.length > 0;
|
||||
|
||||
---
|
||||
|
||||
<DashboardLayout title="Dashboard - Zamaan">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-3xl font-bold">Dashboard</h1>
|
||||
<a href="/dashboard/organizations/new" class="btn btn-outline btn-sm">
|
||||
<DashboardLayout title="Dashboard - Chronus">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent mb-2">
|
||||
Dashboard
|
||||
</h1>
|
||||
<p class="text-base-content/60">Welcome back, {user.name}!</p>
|
||||
</div>
|
||||
<a href="/dashboard/organizations/new" class="btn btn-outline">
|
||||
<Icon name="heroicons:plus" class="w-5 h-5" />
|
||||
New Organization
|
||||
New Team
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="stats shadow border border-base-300">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-primary">
|
||||
<Icon name="heroicons:clock" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="stat-title">This Week</div>
|
||||
<div class="stat-value text-primary text-2xl">{formatDuration(stats.totalTimeThisWeek)}</div>
|
||||
<div class="stat-desc">Total tracked time</div>
|
||||
{!hasMembership && (
|
||||
<div class="alert alert-info mb-8">
|
||||
<Icon name="heroicons:information-circle" class="w-6 h-6" />
|
||||
<div>
|
||||
<h3 class="font-bold">Welcome to Chronus!</h3>
|
||||
<div class="text-sm">You're not part of any team yet. Create one or wait for an invitation.</div>
|
||||
</div>
|
||||
<a href="/dashboard/organizations/new" class="btn btn-primary btn-sm">
|
||||
<Icon name="heroicons:plus" class="w-4 h-4" />
|
||||
New Team
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Icon name="heroicons:calendar" class="w-8 h-8" />
|
||||
{hasMembership && (
|
||||
<>
|
||||
<!-- Stats Overview -->
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow-lg w-full mb-8">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-primary">
|
||||
<Icon name="heroicons:clock" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="stat-title">This Week</div>
|
||||
<div class="stat-value text-primary text-3xl">{formatDuration(stats.totalTimeThisWeek)}</div>
|
||||
<div class="stat-desc">Total tracked time</div>
|
||||
</div>
|
||||
<div class="stat-title">This Month</div>
|
||||
<div class="stat-value text-secondary text-2xl">{formatDuration(stats.totalTimeThisMonth)}</div>
|
||||
<div class="stat-desc">Total tracked time</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-accent">
|
||||
<Icon name="heroicons:play-circle" class="w-8 h-8" />
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary">
|
||||
<Icon name="heroicons:calendar" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="stat-title">This Month</div>
|
||||
<div class="stat-value text-secondary text-3xl">{formatDuration(stats.totalTimeThisMonth)}</div>
|
||||
<div class="stat-desc">Total tracked time</div>
|
||||
</div>
|
||||
<div class="stat-title">Active Timers</div>
|
||||
<div class="stat-value text-accent text-2xl">{stats.activeTimers}</div>
|
||||
<div class="stat-desc">Currently running</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats shadow border border-base-300">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-info">
|
||||
<Icon name="heroicons:building-office" class="w-8 h-8" />
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-accent">
|
||||
<Icon name="heroicons:play-circle" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="stat-title">Active Timers</div>
|
||||
<div class="stat-value text-accent text-3xl">{stats.activeTimers}</div>
|
||||
<div class="stat-desc">Currently running</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-info">
|
||||
<Icon name="heroicons:building-office" class="w-8 h-8" />
|
||||
</div>
|
||||
<div class="stat-title">Clients</div>
|
||||
<div class="stat-value text-info text-3xl">{stats.totalClients}</div>
|
||||
<div class="stat-desc">Total active</div>
|
||||
</div>
|
||||
<div class="stat-title">Clients</div>
|
||||
<div class="stat-value text-info text-2xl">{stats.totalClients}</div>
|
||||
<div class="stat-desc">Total active</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Organizations -->
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:building-office-2" class="w-6 h-6" />
|
||||
Your Organizations
|
||||
</h2>
|
||||
<ul class="menu bg-base-100 w-full p-0">
|
||||
{userOrgs.map(org => (
|
||||
<li>
|
||||
<a class="flex justify-between">
|
||||
<span>{org.name}</span>
|
||||
<span class="badge badge-sm">{org.role}</span>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Quick Actions -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:bolt" class="w-6 h-6 text-warning" />
|
||||
Quick Actions
|
||||
</h2>
|
||||
<div class="flex flex-col gap-3 mt-4">
|
||||
<a href="/dashboard/tracker" class="btn btn-primary">
|
||||
<Icon name="heroicons:play" class="w-5 h-5" />
|
||||
Start Timer
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/dashboard/clients/new" class="btn btn-outline">
|
||||
<Icon name="heroicons:plus" class="w-5 h-5" />
|
||||
Add Client
|
||||
</a>
|
||||
<a href="/dashboard/reports" class="btn btn-outline">
|
||||
<Icon name="heroicons:chart-bar" class="w-5 h-5" />
|
||||
View Reports
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:bolt" class="w-6 h-6" />
|
||||
Quick Actions
|
||||
</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<a href="/dashboard/tracker" class="btn btn-primary">
|
||||
<Icon name="heroicons:play" class="w-5 h-5" />
|
||||
Start Timer
|
||||
</a>
|
||||
<a href="/dashboard/clients/new" class="btn btn-outline">
|
||||
<Icon name="heroicons:plus" class="w-5 h-5" />
|
||||
Add Client
|
||||
</a>
|
||||
<a href="/dashboard/reports" class="btn btn-outline">
|
||||
<Icon name="heroicons:chart-bar" class="w-5 h-5" />
|
||||
View Reports
|
||||
</a>
|
||||
<!-- Recent Activity -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:clock" class="w-6 h-6 text-success" />
|
||||
Recent Activity
|
||||
</h2>
|
||||
{stats.recentEntries.length > 0 ? (
|
||||
<ul class="space-y-3 mt-4">
|
||||
{stats.recentEntries.map(({ entry, client, category }) => (
|
||||
<li class="p-3 rounded-lg bg-base-200 border-l-4 hover:bg-base-300 transition-colors" style={`border-color: ${category.color || '#3b82f6'}`}>
|
||||
<div class="font-semibold text-sm">{client.name}</div>
|
||||
<div class="text-xs text-base-content/60 mt-1">
|
||||
{category.name} • {entry.endTime ? formatDuration(entry.endTime.getTime() - entry.startTime.getTime()) : 'Running...'}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center py-8 text-center mt-4">
|
||||
<Icon name="heroicons:clock" class="w-12 h-12 text-base-content/20 mb-3" />
|
||||
<p class="text-base-content/60 text-sm">No recent time entries</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Icon name="heroicons:clock" class="w-6 h-6" />
|
||||
Recent Activity
|
||||
</h2>
|
||||
{stats.recentEntries.length > 0 ? (
|
||||
<ul class="space-y-2">
|
||||
{stats.recentEntries.map(({ entry, client, category }) => (
|
||||
<li class="text-sm border-l-2 pl-2" style={`border-color: ${category.color || '#3b82f6'}`}>
|
||||
<div class="font-semibold">{client.name}</div>
|
||||
<div class="text-xs text-base-content/60">
|
||||
{category.name} • {entry.endTime ? formatDuration(entry.endTime.getTime() - entry.startTime.getTime()) : 'Running...'}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p class="text-base-content/60 text-sm">No recent time entries</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DashboardLayout>
|
||||
|
||||
Reference in New Issue
Block a user