mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 16:46:41 +02:00
rename(taktik): rebrand to Times
Rename taktik → times across the entire app: package names (@taktik → @times), appId, localStorage keys, export filenames, type names (TaktikSettings → TimesSettings), monorepo scripts, shared-branding, mana-auth trustedOrigins, docker-compose, and documentation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1eb370eaaa
commit
c33339b0cf
92 changed files with 970 additions and 1263 deletions
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: 'Taktik: Production Readiness Audit'
|
||||
title: 'Times: Production Readiness Audit'
|
||||
description: 'Zeiterfassung mit Live-Timer, Projekten, Kunden, Reports, CSV-Export, Templates und Abrechnungsraten - local-first mit umfassender Dokumentation und solider Testabdeckung'
|
||||
date: 2026-03-30
|
||||
app: 'taktik'
|
||||
app: 'times'
|
||||
author: 'Claude Code'
|
||||
tags: ['audit', 'taktik', 'production-readiness', 'beta']
|
||||
tags: ['audit', 'times', 'production-readiness', 'beta']
|
||||
score: 55
|
||||
scores:
|
||||
backend: 5
|
||||
|
|
@ -39,7 +39,7 @@ stats:
|
|||
|
||||
## Zusammenfassung
|
||||
|
||||
Taktik ist eine **vollwertige Zeiterfassung** mit Live-Timer, Projekt-/Kunden-Management, Reports mit Charts, CSV-Export und konfigurierbaren Abrechnungsraten. Local-first mit 6 Dexie-Collections, 4 Testdateien und umfassender CLAUDE.md. Feature-komplett für den Produktiveinsatz.
|
||||
Times ist eine **vollwertige Zeiterfassung** mit Live-Timer, Projekt-/Kunden-Management, Reports mit Charts, CSV-Export und konfigurierbaren Abrechnungsraten. Local-first mit 6 Dexie-Collections, 4 Testdateien und umfassender CLAUDE.md. Feature-komplett für den Produktiveinsatz.
|
||||
|
||||
## Backend (5/100)
|
||||
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"name": "taktik",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Taktik - Zeiterfassung & Timetracking",
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter @taktik/web dev",
|
||||
"dev:web": "pnpm --filter @taktik/web dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.0"
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Taktik
|
||||
# Times
|
||||
|
||||
Zeiterfassung & Timetracking - Dein Arbeitsrhythmus, messbar gemacht.
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ Zeiterfassung & Timetracking - Dein Arbeitsrhythmus, messbar gemacht.
|
|||
|
||||
## Project Overview
|
||||
|
||||
Taktik is a professional time tracking app with timer, manual entry, projects, clients, reports, templates, and guild (team) integration. Built local-first for offline capability and instant UI.
|
||||
Times is a professional time tracking app with timer, manual entry, projects, clients, reports, templates, and guild (team) integration. Built local-first for offline capability and instant UI.
|
||||
|
||||
### Tech Stack
|
||||
|
||||
|
|
@ -24,16 +24,16 @@ Taktik is a professional time tracking app with timer, manual entry, projects, c
|
|||
|
||||
```bash
|
||||
# From monorepo root
|
||||
pnpm dev:taktik:web # Start web app on port 5197
|
||||
pnpm dev:taktik:full # Start with auth + sync server
|
||||
pnpm dev:times:web # Start web app on port 5197
|
||||
pnpm dev:times:full # Start with auth + sync server
|
||||
|
||||
# Tests
|
||||
pnpm --filter @taktik/web test # Run all tests
|
||||
pnpm --filter @taktik/web test:unit # Run in watch mode
|
||||
pnpm --filter @times/web test # Run all tests
|
||||
pnpm --filter @times/web test:unit # Run in watch mode
|
||||
|
||||
# Type checking
|
||||
pnpm --filter @taktik/web type-check
|
||||
pnpm --filter @taktik/shared type-check
|
||||
pnpm --filter @times/web type-check
|
||||
pnpm --filter @times/shared type-check
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
|
@ -104,7 +104,7 @@ pnpm --filter @taktik/shared type-check
|
|||
## Project Structure
|
||||
|
||||
```
|
||||
apps/taktik/
|
||||
apps/times/
|
||||
├── apps/
|
||||
│ └── web/ # SvelteKit web client (port 5197)
|
||||
│ ├── src/
|
||||
|
|
@ -160,7 +160,7 @@ apps/taktik/
|
|||
│ │ └── version.ts
|
||||
│ └── static/
|
||||
├── packages/
|
||||
│ └── shared/ # @taktik/shared
|
||||
│ └── shared/ # @times/shared
|
||||
│ └── src/
|
||||
│ ├── types/index.ts # All TypeScript types
|
||||
│ ├── constants/index.ts # Currencies, colors, defaults
|
||||
|
|
@ -9,15 +9,15 @@ ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
|
|||
ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
|
||||
|
||||
# Copy app-specific packages
|
||||
COPY apps/taktik/packages/shared ./apps/taktik/packages/shared
|
||||
COPY apps/taktik/apps/web ./apps/taktik/apps/web
|
||||
COPY apps/times/packages/shared ./apps/times/packages/shared
|
||||
COPY apps/times/apps/web ./apps/times/apps/web
|
||||
|
||||
# Install app-specific dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --no-frozen-lockfile --ignore-scripts
|
||||
|
||||
# Build the web app
|
||||
WORKDIR /app/apps/taktik/apps/web
|
||||
WORKDIR /app/apps/times/apps/web
|
||||
RUN pnpm exec svelte-kit sync
|
||||
RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build
|
||||
|
||||
|
|
@ -25,17 +25,17 @@ RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build
|
|||
FROM node:20-alpine AS production
|
||||
|
||||
# Keep same directory structure as builder so pnpm symlinks resolve correctly
|
||||
WORKDIR /app/apps/taktik/apps/web
|
||||
WORKDIR /app/apps/times/apps/web
|
||||
|
||||
# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
|
||||
COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
|
||||
|
||||
# Copy the app's node_modules (contains symlinks to the pnpm store)
|
||||
COPY --from=builder /app/apps/taktik/apps/web/node_modules ./node_modules
|
||||
COPY --from=builder /app/apps/times/apps/web/node_modules ./node_modules
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app/apps/taktik/apps/web/build ./build
|
||||
COPY --from=builder /app/apps/taktik/apps/web/package.json ./
|
||||
COPY --from=builder /app/apps/times/apps/web/build ./build
|
||||
COPY --from=builder /app/apps/times/apps/web/package.json ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5027
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@taktik/web",
|
||||
"name": "@times/web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"@manacore/shared-types": "workspace:*",
|
||||
"@manacore/shared-ui": "workspace:*",
|
||||
"@manacore/shared-utils": "workspace:*",
|
||||
"@taktik/shared": "workspace:*",
|
||||
"@times/shared": "workspace:*",
|
||||
"date-fns": "^4.1.0",
|
||||
"svelte-i18n": "^4.0.1"
|
||||
},
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
|
||||
<title>Taktik</title>
|
||||
<title>Times</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { timeEntryCollection } from '$lib/data/local-store';
|
||||
import type { Project, Client } from '@taktik/shared';
|
||||
import type { Project, Client } from '@times/shared';
|
||||
|
||||
let {
|
||||
visible = false,
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { timeEntryCollection } from '$lib/data/local-store';
|
||||
import { formatDurationCompact } from '$lib/data/queries';
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
import ConfirmDialog from './ConfirmDialog.svelte';
|
||||
|
||||
let {
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import EntryItem from './EntryItem.svelte';
|
||||
import { groupEntriesByDate, getTotalDuration, formatDurationCompact } from '$lib/data/queries';
|
||||
import type { TimeEntry } from '@taktik/shared';
|
||||
import type { TimeEntry } from '@times/shared';
|
||||
|
||||
let { entries }: { entries: TimeEntry[] } = $props();
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { timerStore } from '$lib/stores/timer.svelte';
|
||||
import type { TimeEntry, Project } from '@taktik/shared';
|
||||
import type { TimeEntry, Project } from '@times/shared';
|
||||
|
||||
const allTimeEntries = getContext<{ value: TimeEntry[] }>('timeEntries');
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { timerStore } from '$lib/stores/timer.svelte';
|
||||
import { formatDuration } from '$lib/data/queries';
|
||||
import type { Project, Client } from '@taktik/shared';
|
||||
import type { Project, Client } from '@times/shared';
|
||||
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
const allClients = getContext<{ value: Client[] }>('clients');
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { timerStore } from '$lib/stores/timer.svelte';
|
||||
import { formatDuration } from '$lib/data/queries';
|
||||
import type { Project } from '@taktik/shared';
|
||||
import type { Project } from '@times/shared';
|
||||
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Guest seed data for the Taktik app.
|
||||
* Guest seed data for the Times app.
|
||||
*
|
||||
* Provides demo clients, projects, and time entries for the guest experience.
|
||||
*/
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Taktik — Local-First Data Layer
|
||||
* Times — Local-First Data Layer
|
||||
*
|
||||
* IndexedDB (Dexie.js) with sync support for time tracking.
|
||||
* Clients, projects, time entries, tags, templates, and settings.
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
guestTags,
|
||||
guestSettings,
|
||||
} from './guest-seed';
|
||||
import type { BillingRate, ProjectVisibility, EntrySourceRef } from '@taktik/shared';
|
||||
import type { BillingRate, ProjectVisibility, EntrySourceRef } from '@times/shared';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -97,8 +97,8 @@ export interface LocalSettings extends BaseRecord {
|
|||
|
||||
const SYNC_SERVER_URL = import.meta.env.PUBLIC_SYNC_SERVER_URL || 'http://localhost:3050';
|
||||
|
||||
export const taktikStore = createLocalStore({
|
||||
appId: 'taktik',
|
||||
export const timesStore = createLocalStore({
|
||||
appId: 'times',
|
||||
collections: [
|
||||
{
|
||||
name: 'clients',
|
||||
|
|
@ -145,9 +145,9 @@ export const taktikStore = createLocalStore({
|
|||
});
|
||||
|
||||
// Typed collection accessors
|
||||
export const clientCollection = taktikStore.collection<LocalClient>('clients');
|
||||
export const projectCollection = taktikStore.collection<LocalProject>('projects');
|
||||
export const timeEntryCollection = taktikStore.collection<LocalTimeEntry>('timeEntries');
|
||||
export const tagCollection = taktikStore.collection<LocalTag>('tags');
|
||||
export const templateCollection = taktikStore.collection<LocalTemplate>('templates');
|
||||
export const settingsCollection = taktikStore.collection<LocalSettings>('settings');
|
||||
export const clientCollection = timesStore.collection<LocalClient>('clients');
|
||||
export const projectCollection = timesStore.collection<LocalProject>('projects');
|
||||
export const timeEntryCollection = timesStore.collection<LocalTimeEntry>('timeEntries');
|
||||
export const tagCollection = timesStore.collection<LocalTag>('tags');
|
||||
export const templateCollection = timesStore.collection<LocalTemplate>('templates');
|
||||
export const settingsCollection = timesStore.collection<LocalSettings>('settings');
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
getClientById,
|
||||
getProjectsByClient,
|
||||
} from './queries';
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
|
||||
// ─── Test Factories ──────────────────────────────────────
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Reactive Queries & Pure Helpers for Taktik
|
||||
* Reactive Queries & Pure Helpers for Times
|
||||
*
|
||||
* Uses Dexie liveQuery to automatically re-render when IndexedDB changes
|
||||
* (local writes, sync updates, other tabs).
|
||||
|
|
@ -26,10 +26,10 @@ import type {
|
|||
TimeEntry,
|
||||
Tag,
|
||||
EntryTemplate,
|
||||
TaktikSettings,
|
||||
TimesSettings,
|
||||
FilterCriteria,
|
||||
SortOption,
|
||||
} from '@taktik/shared';
|
||||
} from '@times/shared';
|
||||
|
||||
// ─── Type Converters ───────────────────────────────────────
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ export function toTemplate(local: LocalTemplate): EntryTemplate {
|
|||
};
|
||||
}
|
||||
|
||||
export function toSettings(local: LocalSettings): TaktikSettings {
|
||||
export function toSettings(local: LocalSettings): TimesSettings {
|
||||
return {
|
||||
id: local.id,
|
||||
defaultBillingRate: local.defaultBillingRate ?? undefined,
|
||||
|
|
@ -178,7 +178,7 @@ export function useSettings() {
|
|||
const locals = await settingsCollection.getAll();
|
||||
return locals.length > 0 ? toSettings(locals[0]) : null;
|
||||
},
|
||||
null as TaktikSettings | null
|
||||
null as TimesSettings | null
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -5,11 +5,11 @@ import type {
|
|||
TimeEntry,
|
||||
Tag,
|
||||
EntryTemplate,
|
||||
TaktikSettings,
|
||||
TimesSettings,
|
||||
BillingRate,
|
||||
FilterCriteria,
|
||||
SortOption,
|
||||
} from '@taktik/shared';
|
||||
} from '@times/shared';
|
||||
|
||||
describe('Shared Types', () => {
|
||||
it('BillingRate has correct shape', () => {
|
||||
|
|
@ -11,7 +11,7 @@ register('en', () => import('./locales/en.json'));
|
|||
|
||||
function getInitialLocale(): SupportedLocale {
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('taktik_locale');
|
||||
const stored = localStorage.getItem('times_locale');
|
||||
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
|
||||
return stored as SupportedLocale;
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ init({
|
|||
export function setLocale(newLocale: SupportedLocale) {
|
||||
locale.set(newLocale);
|
||||
if (browser) {
|
||||
localStorage.setItem('taktik_locale', newLocale);
|
||||
localStorage.setItem('times_locale', newLocale);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Taktik",
|
||||
"name": "Times",
|
||||
"loading": "Laden...",
|
||||
"tagline": "Dein Arbeitsrhythmus, messbar gemacht."
|
||||
},
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"app": {
|
||||
"name": "Taktik",
|
||||
"name": "Times",
|
||||
"loading": "Loading...",
|
||||
"tagline": "Your work rhythm, made measurable."
|
||||
},
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { createThemeStore } from '@manacore/shared-theme';
|
||||
|
||||
export const theme = createThemeStore({
|
||||
appId: 'taktik',
|
||||
appId: 'times',
|
||||
defaultVariant: 'ocean',
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@ function getAuthUrl(): string {
|
|||
}
|
||||
|
||||
export const userSettings = createUserSettingsStore({
|
||||
appId: 'taktik',
|
||||
appId: 'times',
|
||||
authUrl: getAuthUrl,
|
||||
getAccessToken: () => authStore.getAccessToken(),
|
||||
});
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { browser } from '$app/environment';
|
||||
import type { ViewMode, SortOption, FilterCriteria, SavedFilter } from '@taktik/shared';
|
||||
import type { ViewMode, SortOption, FilterCriteria, SavedFilter } from '@times/shared';
|
||||
|
||||
const VIEW_KEY = 'taktik_view_mode';
|
||||
const SORT_KEY = 'taktik_sort';
|
||||
const FILTERS_KEY = 'taktik_saved_filters';
|
||||
const VIEW_KEY = 'times_view_mode';
|
||||
const SORT_KEY = 'times_sort';
|
||||
const FILTERS_KEY = 'times_saved_filters';
|
||||
|
||||
function load<T>(key: string, fallback: T): T {
|
||||
if (!browser) return fallback;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
|
||||
// We test the CSV generation logic without triggering DOM download.
|
||||
// This mirrors the core logic from export.ts.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* CSV Export utility for time entries
|
||||
*/
|
||||
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
|
||||
export function exportEntriesToCSV(
|
||||
entries: TimeEntry[],
|
||||
|
|
@ -55,7 +55,7 @@ export function exportEntriesToCSV(
|
|||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `taktik-export-${new Date().toISOString().split('T')[0]}.csv`;
|
||||
a.download = `times-export-${new Date().toISOString().split('T')[0]}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* Applies rounding based on user settings (increment + method).
|
||||
*/
|
||||
|
||||
import type { RoundingMethod } from '@taktik/shared';
|
||||
import type { RoundingMethod } from '@times/shared';
|
||||
|
||||
/**
|
||||
* Round a duration in seconds based on settings.
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
import { getPillAppItems } from '@manacore/shared-branding';
|
||||
import { AuthGate, GuestWelcomeModal } from '@manacore/shared-auth-ui';
|
||||
import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui';
|
||||
import { taktikStore } from '$lib/data/local-store';
|
||||
import { timesStore } from '$lib/data/local-store';
|
||||
import {
|
||||
useAllClients,
|
||||
useAllProjects,
|
||||
|
|
@ -46,17 +46,17 @@
|
|||
setContext('settings', settings);
|
||||
|
||||
async function handleAuthReady() {
|
||||
await taktikStore.initialize();
|
||||
await timesStore.initialize();
|
||||
|
||||
if (authStore.isAuthenticated) {
|
||||
taktikStore.startSync(() => authStore.getValidToken());
|
||||
timesStore.startSync(() => authStore.getValidToken());
|
||||
}
|
||||
|
||||
viewStore.initialize();
|
||||
await timerStore.initialize();
|
||||
initialized = true;
|
||||
|
||||
if (!authStore.isAuthenticated && shouldShowGuestWelcome('taktik')) {
|
||||
if (!authStore.isAuthenticated && shouldShowGuestWelcome('times')) {
|
||||
showGuestWelcome = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-lg font-bold text-[hsl(var(--foreground))]">Taktik</span>
|
||||
<span class="text-lg font-bold text-[hsl(var(--foreground))]">Times</span>
|
||||
</a>
|
||||
|
||||
<!-- Nav Items -->
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
</button>
|
||||
|
||||
<GuestWelcomeModal
|
||||
appId="taktik"
|
||||
appId="times"
|
||||
visible={showGuestWelcome}
|
||||
onClose={() => (showGuestWelcome = false)}
|
||||
onLogin={() => goto('/login')}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
import {
|
||||
getEntriesByDate,
|
||||
getTotalDuration,
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Timer | Taktik</title>
|
||||
<title>Timer | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { clientCollection } from '$lib/data/local-store';
|
||||
import { getTotalDuration, formatDurationCompact } from '$lib/data/queries';
|
||||
import type { Client, Project, TimeEntry } from '@taktik/shared';
|
||||
import { PROJECT_COLORS } from '@taktik/shared/constants';
|
||||
import type { Client, Project, TimeEntry } from '@times/shared';
|
||||
import { PROJECT_COLORS } from '@times/shared/constants';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
|
||||
const allClients = getContext<{ value: Client[] }>('clients');
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('nav.clients')} | Taktik</title>
|
||||
<title>{$_('nav.clients')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
formatDurationDecimal,
|
||||
} from '$lib/data/queries';
|
||||
import EntryList from '$lib/components/EntryList.svelte';
|
||||
import type { Project, Client, TimeEntry } from '@taktik/shared';
|
||||
import type { Project, Client, TimeEntry } from '@times/shared';
|
||||
|
||||
const allClients = getContext<{ value: Client[] }>('clients');
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{client?.name || 'Kunde'} | Taktik</title>
|
||||
<title>{client?.name || 'Kunde'} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if !client}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { TimeEntry } from '@taktik/shared';
|
||||
import type { TimeEntry } from '@times/shared';
|
||||
import {
|
||||
getFilteredEntries,
|
||||
getSortedEntries,
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('nav.entries')} | Taktik</title>
|
||||
<title>{$_('nav.entries')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Feedback | Taktik</title>
|
||||
<title>Feedback | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Hilfe | Taktik</title>
|
||||
<title>Hilfe | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Mana | Taktik</title>
|
||||
<title>Mana | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Profil | Taktik</title>
|
||||
<title>Profil | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { projectCollection } from '$lib/data/local-store';
|
||||
import { getTotalDuration, formatDurationCompact } from '$lib/data/queries';
|
||||
import type { Project, Client, TimeEntry } from '@taktik/shared';
|
||||
import { PROJECT_COLORS } from '@taktik/shared/constants';
|
||||
import type { Project, Client, TimeEntry } from '@times/shared';
|
||||
import { PROJECT_COLORS } from '@times/shared/constants';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('nav.projects')} | Taktik</title>
|
||||
<title>{$_('nav.projects')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
formatDurationDecimal,
|
||||
} from '$lib/data/queries';
|
||||
import EntryList from '$lib/components/EntryList.svelte';
|
||||
import type { Project, Client, TimeEntry } from '@taktik/shared';
|
||||
import { PROJECT_COLORS } from '@taktik/shared/constants';
|
||||
import type { Project, Client, TimeEntry } from '@times/shared';
|
||||
import { PROJECT_COLORS } from '@times/shared/constants';
|
||||
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
const allClients = getContext<{ value: Client[] }>('clients');
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{project?.name || 'Projekt'} | Taktik</title>
|
||||
<title>{project?.name || 'Projekt'} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if !project}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import type { TimeEntry, Project, Client } from '@taktik/shared';
|
||||
import type { TimeEntry, Project, Client } from '@times/shared';
|
||||
import { exportEntriesToCSV } from '$lib/utils/export';
|
||||
import {
|
||||
getTotalDuration,
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('nav.reports')} | Taktik</title>
|
||||
<title>{$_('nav.reports')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
import { getContext } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { settingsCollection } from '$lib/data/local-store';
|
||||
import type { TaktikSettings } from '@taktik/shared';
|
||||
import { CURRENCIES, ROUNDING_INCREMENTS } from '@taktik/shared/constants';
|
||||
import type { TimesSettings } from '@times/shared';
|
||||
import { CURRENCIES, ROUNDING_INCREMENTS } from '@times/shared/constants';
|
||||
|
||||
const settings = getContext<{ value: TaktikSettings | null }>('settings');
|
||||
const settings = getContext<{ value: TimesSettings | null }>('settings');
|
||||
|
||||
// Local edit state, synced from settings
|
||||
let workingHoursPerDay = $state(8);
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('settings.title')} | Taktik</title>
|
||||
<title>{$_('settings.title')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { _ } from 'svelte-i18n';
|
||||
import { templateCollection, timeEntryCollection } from '$lib/data/local-store';
|
||||
import { timerStore } from '$lib/stores/timer.svelte';
|
||||
import type { EntryTemplate, Project, Client } from '@taktik/shared';
|
||||
import type { EntryTemplate, Project, Client } from '@times/shared';
|
||||
|
||||
const allTemplates = getContext<{ value: EntryTemplate[] }>('templates');
|
||||
const allProjects = getContext<{ value: Project[] }>('projects');
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('nav.templates')} | Taktik</title>
|
||||
<title>{$_('nav.templates')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Themes | Taktik</title>
|
||||
<title>Themes | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{showRegister ? $_('auth.register') : $_('auth.login')} | Taktik</title>
|
||||
<title>{showRegister ? $_('auth.register') : $_('auth.login')} | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex min-h-screen items-center justify-center bg-[hsl(var(--background))] p-4">
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-[hsl(var(--foreground))]">Taktik</h1>
|
||||
<h1 class="text-2xl font-bold text-[hsl(var(--foreground))]">Times</h1>
|
||||
<p class="mt-1 text-sm text-[hsl(var(--muted-foreground))]">Zeiterfassung</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -256,7 +256,7 @@
|
|||
<!-- App switcher -->
|
||||
<div class="mt-6 flex flex-wrap justify-center gap-2">
|
||||
{#each getPillAppItems() as app}
|
||||
{#if app.id !== 'taktik'}
|
||||
{#if app.id !== 'times'}
|
||||
<a
|
||||
href={app.url}
|
||||
class="rounded-full border border-[hsl(var(--border))] px-3 py-1 text-xs text-[hsl(var(--muted-foreground))] transition-colors hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]"
|
||||
|
|
@ -5,6 +5,6 @@ export const GET: RequestHandler = async () => {
|
|||
return json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'taktik-web',
|
||||
service: 'times-web',
|
||||
});
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Offline | Taktik</title>
|
||||
<title>Offline | Times</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex min-h-screen items-center justify-center bg-[hsl(var(--background))] p-4">
|
||||
|
|
@ -11,8 +11,8 @@ export default defineConfig({
|
|||
SvelteKitPWA({
|
||||
registerType: 'autoUpdate',
|
||||
manifest: {
|
||||
name: 'Taktik',
|
||||
short_name: 'Taktik',
|
||||
name: 'Times',
|
||||
short_name: 'Times',
|
||||
description: 'Zeiterfassung & Timetracking',
|
||||
theme_color: '#f59e0b',
|
||||
background_color: '#0f172a',
|
||||
14
apps/times/package.json
Normal file
14
apps/times/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "times",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Times - Zeiterfassung & Timetracking",
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter @times/web dev",
|
||||
"dev:web": "pnpm --filter @times/web dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.0"
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@taktik/shared",
|
||||
"name": "@times/shared",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
@ -110,7 +110,7 @@ export interface EntryTemplate {
|
|||
|
||||
export type RoundingMethod = 'none' | 'up' | 'down' | 'nearest';
|
||||
|
||||
export interface TaktikSettings {
|
||||
export interface TimesSettings {
|
||||
id: string;
|
||||
defaultBillingRate?: BillingRate;
|
||||
workingHoursPerDay: number;
|
||||
Loading…
Add table
Add a link
Reference in a new issue