Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
274 lines
9.0 KiB
Plaintext
274 lines
9.0 KiB
Plaintext
---
|
|
import DashboardLayout from '../../../layouts/DashboardLayout.astro';
|
|
import { Icon } from 'astro-icon/components';
|
|
import { db } from '../../../db';
|
|
import { categories, members, organizations } from '../../../db/schema';
|
|
import { eq } from 'drizzle-orm';
|
|
|
|
const user = Astro.locals.user;
|
|
if (!user) return Astro.redirect('/login');
|
|
|
|
// Get current team from cookie
|
|
const currentTeamId = Astro.cookies.get('currentTeamId')?.value;
|
|
|
|
const userMemberships = await db.select()
|
|
.from(members)
|
|
.where(eq(members.userId, user.id))
|
|
.all();
|
|
|
|
if (userMemberships.length === 0) return Astro.redirect('/dashboard');
|
|
|
|
// Use current team or fallback to first membership
|
|
const userMembership = currentTeamId
|
|
? userMemberships.find(m => m.organizationId === currentTeamId) || userMemberships[0]
|
|
: userMemberships[0];
|
|
|
|
const isAdmin = userMembership.role === 'owner' || userMembership.role === 'admin';
|
|
if (!isAdmin) return Astro.redirect('/dashboard/team');
|
|
|
|
const orgId = userMembership.organizationId;
|
|
|
|
const organization = await db.select()
|
|
.from(organizations)
|
|
.where(eq(organizations.id, orgId))
|
|
.get();
|
|
|
|
if (!organization) return Astro.redirect('/dashboard');
|
|
|
|
const allCategories = await db.select()
|
|
.from(categories)
|
|
.where(eq(categories.organizationId, orgId))
|
|
.all();
|
|
|
|
const url = new URL(Astro.request.url);
|
|
const successType = url.searchParams.get('success');
|
|
---
|
|
|
|
<DashboardLayout title="Team Settings - Chronus">
|
|
<div class="flex items-center gap-3 mb-6">
|
|
<a href="/dashboard/team" class="btn btn-ghost btn-sm">
|
|
<Icon name="heroicons:arrow-left" class="w-5 h-5" />
|
|
</a>
|
|
<h1 class="text-3xl font-bold">Team Settings</h1>
|
|
</div>
|
|
|
|
<!-- Team Settings -->
|
|
<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-2" class="w-6 h-6" />
|
|
Team Settings
|
|
</h2>
|
|
|
|
{successType === 'org-name' && (
|
|
<div class="alert alert-success mb-4">
|
|
<Icon name="heroicons:check-circle" class="w-6 h-6" />
|
|
<span>Team information updated successfully!</span>
|
|
</div>
|
|
)}
|
|
|
|
<form
|
|
action="/api/organizations/update-name"
|
|
method="POST"
|
|
class="space-y-4"
|
|
enctype="multipart/form-data"
|
|
>
|
|
<input type="hidden" name="organizationId" value={organization.id} />
|
|
|
|
<div class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">Team Logo</span>
|
|
</div>
|
|
<div class="flex items-center gap-6">
|
|
<div class="avatar placeholder">
|
|
<div class="bg-base-200 text-neutral-content rounded-xl w-24 border border-base-300 flex items-center justify-center overflow-hidden">
|
|
{organization.logoUrl ? (
|
|
<img
|
|
src={organization.logoUrl}
|
|
alt={organization.name}
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
) : (
|
|
<Icon
|
|
name="heroicons:photo"
|
|
class="w-8 h-8 opacity-40 text-base-content"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<input
|
|
type="file"
|
|
name="logo"
|
|
accept="image/png, image/jpeg"
|
|
class="file-input file-input-bordered w-full max-w-xs"
|
|
/>
|
|
<div class="text-xs text-base-content/60 mt-2">
|
|
Upload a company logo (PNG, JPG).
|
|
<br />
|
|
Will be displayed on invoices and quotes.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">Team Name</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={organization.name}
|
|
placeholder="Organization name"
|
|
class="input input-bordered w-full"
|
|
required
|
|
/>
|
|
<div class="label">
|
|
<span class="label-text-alt text-base-content/60">This name is visible to all team members</span>
|
|
</div>
|
|
</label>
|
|
|
|
<div class="divider">Address Information</div>
|
|
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">Street Address</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="street"
|
|
value={organization.street || ''}
|
|
placeholder="123 Main Street"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</label>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">City</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="city"
|
|
value={organization.city || ''}
|
|
placeholder="City"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</label>
|
|
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">State/Province</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="state"
|
|
value={organization.state || ''}
|
|
placeholder="State/Province"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">Postal Code</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="zip"
|
|
value={organization.zip || ''}
|
|
placeholder="12345"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</label>
|
|
|
|
<label class="form-control">
|
|
<div class="label">
|
|
<span class="label-text font-medium">Country</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="country"
|
|
value={organization.country || ''}
|
|
placeholder="Country"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6">
|
|
<span class="text-xs text-base-content/60 text-center sm:text-left">
|
|
Address information appears on invoices and quotes
|
|
</span>
|
|
|
|
<button type="submit" class="btn btn-primary w-full sm:w-auto">
|
|
<Icon name="heroicons:check" class="w-5 h-5" />
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories Section -->
|
|
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
|
<div class="card-body">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="card-title">
|
|
<Icon name="heroicons:tag" class="w-6 h-6" />
|
|
Work Categories
|
|
</h2>
|
|
<a href="/dashboard/team/settings/categories/new" class="btn btn-primary btn-sm">
|
|
<Icon name="heroicons:plus" class="w-5 h-5" />
|
|
Add Category
|
|
</a>
|
|
</div>
|
|
|
|
<p class="text-base-content/70 mb-4">
|
|
Categories help organize time tracking by type of work. All team members use the same categories.
|
|
</p>
|
|
|
|
{allCategories.length === 0 ? (
|
|
<div class="alert alert-info">
|
|
<Icon name="heroicons:information-circle" class="w-6 h-6" />
|
|
<div>
|
|
<div class="font-bold">No categories yet</div>
|
|
<div class="text-sm">Create your first category to start organizing time entries.</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{allCategories.map(category => (
|
|
<div class="card bg-base-200 border border-base-300">
|
|
<div class="card-body p-4">
|
|
<div class="flex items-center gap-3">
|
|
{category.color && (
|
|
<span class="w-4 h-4 rounded-full shrink-0" style={`background-color: ${category.color}`}></span>
|
|
)}
|
|
<div class="grow min-w-0">
|
|
<h3 class="font-semibold truncate">{category.name}</h3>
|
|
<p class="text-xs text-base-content/60">
|
|
Created {category.createdAt?.toLocaleDateString() ?? 'N/A'}
|
|
</p>
|
|
</div>
|
|
<a
|
|
href={`/dashboard/team/settings/categories/${category.id}/edit`}
|
|
class="btn btn-ghost btn-xs"
|
|
>
|
|
<Icon name="heroicons:pencil" class="w-4 h-4" />
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
</DashboardLayout>
|