Compare commits

..

9 Commits

Author SHA1 Message Date
Constantin Graf
0a28be83a1 Add more tests; Add filter in filament resource; Added options for user create command 2025-02-06 12:15:49 -05:00
Constantin Graf
4eb716d2cc Fixed bugs causing incorrect computed attributes in imported data 2025-02-04 19:51:54 -05:00
Constantin Graf
55323fa4b4 Add missing serve option to local filesystem disk 2025-02-04 19:51:23 -05:00
Constantin Graf
6df20ed1e5 Updated composer dependencies 2025-02-04 19:50:34 -05:00
Constantin Graf
bc7c564eb2 Added estimated time to clockify project import 2025-02-04 13:45:10 -05:00
Constantin Graf
5423b03201 Fixed timezones in unit tests 2024-12-20 19:47:12 -05:00
Constantin Graf
0e910ba565 Updated composer dependencies 2024-12-20 19:28:18 -05:00
Constantin Graf
bad1cd1343 Fixed reports in deletion service 2024-12-20 19:28:10 -05:00
Constantin Graf
dd312b396b Deactivated registration 2024-12-20 19:05:21 -05:00
149 changed files with 3373 additions and 3646 deletions

13
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,13 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
extends: ['plugin:vue/vue3-essential', '@vue/eslint-config-typescript/recommended', '@vue/eslint-config-prettier'],
rules: {
'vue/multi-word-component-names': 'off',
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": "error",
},
plugins: ['unused-imports'],
}

View File

@@ -14,7 +14,7 @@ on:
name: Build - Public name: Build - Public
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
contents: read contents: read

View File

@@ -63,7 +63,7 @@ jobs:
run: php artisan test --stop-on-failure --coverage-text --coverage-clover=coverage.xml run: php artisan test --stop-on-failure --coverage-text --coverage-clover=coverage.xml
- name: "Upload coverage reports to Codecov" - name: "Upload coverage reports to Codecov"
uses: codecov/codecov-action@v5.3.1 uses: codecov/codecov-action@v4.5.0
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
slug: solidtime-io/solidtime slug: solidtime-io/solidtime

View File

@@ -10,6 +10,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: "Check code style" - name: "Check code style"
uses: aglipanci/laravel-pint-action@2.5 uses: aglipanci/laravel-pint-action@2.4
with: with:
configPath: "pint.json" configPath: "pint.json"

View File

@@ -57,7 +57,7 @@ class UserCreateCommand extends Command
} }
$user = null; $user = null;
DB::transaction(function () use (&$user, $name, $email, $password, $verifyEmail): void { DB::transaction(function () use (&$user, $name, $email, $password): void {
$user = app(UserService::class)->createUser( $user = app(UserService::class)->createUser(
$name, $name,
$email, $email,
@@ -65,7 +65,6 @@ class UserCreateCommand extends Command
'UTC', 'UTC',
Weekday::Monday, Weekday::Monday,
'EUR', 'EUR',
$verifyEmail
); );
}); });
/** @var Organization|null $organization */ /** @var Organization|null $organization */
@@ -74,6 +73,10 @@ class UserCreateCommand extends Command
throw new LogicException('User does not have an organization'); throw new LogicException('User does not have an organization');
} }
if ($verifyEmail) {
$user->markEmailAsVerified();
}
$this->info('Created user "'.$name.'" ("'.$email.'")'); $this->info('Created user "'.$name.'" ("'.$email.'")');
$this->line('ID: '.$user->getKey()); $this->line('ID: '.$user->getKey());
$this->line('Name: '.$name); $this->line('Name: '.$name);

View File

@@ -25,7 +25,6 @@ use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Korridor\LaravelModelValidationRules\Rules\UniqueEloquent;
use STS\FilamentImpersonate\Tables\Actions\Impersonate; use STS\FilamentImpersonate\Tables\Actions\Impersonate;
class UserResource extends Resource class UserResource extends Resource
@@ -40,8 +39,6 @@ class UserResource extends Resource
public static function form(Form $form): Form public static function form(Form $form): Form
{ {
/** @var User|null $record */
$record = $form->getRecord();
return $form return $form
->columns(1) ->columns(1)
->schema([ ->schema([
@@ -58,13 +55,6 @@ class UserResource extends Resource
Forms\Components\TextInput::make('email') Forms\Components\TextInput::make('email')
->label('Email') ->label('Email')
->required() ->required()
->rules($record?->is_placeholder ? [] : [
UniqueEloquent::make(User::class, 'email')
->ignore($record?->getKey()),
])
->rule([
'email',
])
->maxLength(255), ->maxLength(255),
Forms\Components\Toggle::make('is_placeholder') Forms\Components\Toggle::make('is_placeholder')
->label('Is Placeholder?') ->label('Is Placeholder?')
@@ -72,11 +62,7 @@ class UserResource extends Resource
->disabledOn(['edit']), ->disabledOn(['edit']),
Forms\Components\DateTimePicker::make('email_verified_at') Forms\Components\DateTimePicker::make('email_verified_at')
->label('Email Verified At') ->label('Email Verified At')
->hiddenOn(['create'])
->nullable(), ->nullable(),
Forms\Components\Toggle::make('is_email_verified')
->label('Email Verified?')
->visibleOn(['create']),
Forms\Components\Select::make('timezone') Forms\Components\Select::make('timezone')
->label('Timezone') ->label('Timezone')
->options(fn (): array => app(TimezoneService::class)->getSelectOptions()) ->options(fn (): array => app(TimezoneService::class)->getSelectOptions())
@@ -88,16 +74,8 @@ class UserResource extends Resource
->required(), ->required(),
TextInput::make('password') TextInput::make('password')
->password() ->password()
->label('Password')
->dehydrateStateUsing(fn ($state) => Hash::make($state)) ->dehydrateStateUsing(fn ($state) => Hash::make($state))
->dehydrated(fn ($state) => filled($state)) ->dehydrated(fn ($state) => filled($state))
->hiddenOn(['create'])
->required(fn (string $context): bool => $context === 'create')
->maxLength(255),
TextInput::make('password_create')
->password()
->label('Password')
->visibleOn(['create'])
->required(fn (string $context): bool => $context === 'create') ->required(fn (string $context): bool => $context === 'create')
->maxLength(255), ->maxLength(255),
Forms\Components\Select::make('currency') Forms\Components\Select::make('currency')

View File

@@ -20,11 +20,10 @@ class CreateUser extends CreateRecord
$user = $userService->createUser( $user = $userService->createUser(
$data['name'], $data['name'],
$data['email'], $data['email'],
$data['password_create'], $data['password'],
$data['timezone'], $data['timezone'],
Weekday::from($data['week_start']), Weekday::from($data['week_start']),
$data['currency'], $data['currency'],
(bool) $data['is_email_verified']
); );
return $user; return $user;

View File

@@ -34,7 +34,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
* @property string $id * @property string $id
* @property string $name * @property string $name
* @property string $email * @property string $email
* @property Carbon|null $email_verified_at * @property string|null $email_verified_at
* @property string|null $password * @property string|null $password
* @property string|null $two_factor_secret * @property string|null $two_factor_secret
* @property string $timezone * @property string $timezone

View File

@@ -12,12 +12,11 @@ use App\Models\Organization;
use App\Models\ProjectMember; use App\Models\ProjectMember;
use App\Models\TimeEntry; use App\Models\TimeEntry;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class UserService class UserService
{ {
public function createUser(string $name, string $email, string $password, string $timezone, Weekday $weekStart, string $currency, bool $verifyEmail = false): User public function createUser(string $name, string $email, string $password, string $timezone, Weekday $weekStart, string $currency): User
{ {
$user = new User; $user = new User;
$user->name = $name; $user->name = $name;
@@ -25,9 +24,6 @@ class UserService
$user->password = Hash::make($password); $user->password = Hash::make($password);
$user->timezone = $timezone; $user->timezone = $timezone;
$user->week_start = $weekStart; $user->week_start = $weekStart;
if ($verifyEmail) {
$user->email_verified_at = Carbon::now();
}
$user->save(); $user->save();
$organization = new Organization; $organization = new Organization;

116
composer.lock generated
View File

@@ -128,16 +128,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.339.7", "version": "3.339.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "7b7e48ce7970c0416c5fda045df7b93948fbf643" "reference": "cc0b21de3b1eaabb7d0a1ed4f3f067bad4be5f31"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7b7e48ce7970c0416c5fda045df7b93948fbf643", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/cc0b21de3b1eaabb7d0a1ed4f3f067bad4be5f31",
"reference": "7b7e48ce7970c0416c5fda045df7b93948fbf643", "reference": "cc0b21de3b1eaabb7d0a1ed4f3f067bad4be5f31",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -220,9 +220,9 @@
"support": { "support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions", "forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.339.7" "source": "https://github.com/aws/aws-sdk-php/tree/3.339.6"
}, },
"time": "2025-02-05T19:06:15+00:00" "time": "2025-02-04T19:03:40+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@@ -692,16 +692,16 @@
}, },
{ {
"name": "composer/class-map-generator", "name": "composer/class-map-generator",
"version": "1.6.0", "version": "1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/class-map-generator.git", "url": "https://github.com/composer/class-map-generator.git",
"reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9" "reference": "4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/class-map-generator/zipball/ffe442c5974c44a9343e37a0abcb1cc37319f5b9", "url": "https://api.github.com/repos/composer/class-map-generator/zipball/4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915",
"reference": "ffe442c5974c44a9343e37a0abcb1cc37319f5b9", "reference": "4b0a223cf5be7c9ee7e0ef1bc7db42b4a97c9915",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -745,7 +745,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/composer/class-map-generator/issues", "issues": "https://github.com/composer/class-map-generator/issues",
"source": "https://github.com/composer/class-map-generator/tree/1.6.0" "source": "https://github.com/composer/class-map-generator/tree/1.5.0"
}, },
"funding": [ "funding": [
{ {
@@ -761,7 +761,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-02-05T10:05:34+00:00" "time": "2024-11-25T16:11:06+00:00"
}, },
{ {
"name": "composer/composer", "name": "composer/composer",
@@ -1519,16 +1519,16 @@
}, },
{ {
"name": "dedoc/scramble", "name": "dedoc/scramble",
"version": "v0.12.5", "version": "v0.12.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/dedoc/scramble.git", "url": "https://github.com/dedoc/scramble.git",
"reference": "de055584b61338dd6de6cc1cc20d79da6a442230" "reference": "9482d3c6285ebb16a067bd28b01e94899e1fddd8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/dedoc/scramble/zipball/de055584b61338dd6de6cc1cc20d79da6a442230", "url": "https://api.github.com/repos/dedoc/scramble/zipball/9482d3c6285ebb16a067bd28b01e94899e1fddd8",
"reference": "de055584b61338dd6de6cc1cc20d79da6a442230", "reference": "9482d3c6285ebb16a067bd28b01e94899e1fddd8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1583,7 +1583,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/dedoc/scramble/issues", "issues": "https://github.com/dedoc/scramble/issues",
"source": "https://github.com/dedoc/scramble/tree/v0.12.5" "source": "https://github.com/dedoc/scramble/tree/v0.12.2"
}, },
"funding": [ "funding": [
{ {
@@ -1591,7 +1591,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-02-05T11:22:52+00:00" "time": "2025-02-03T12:23:33+00:00"
}, },
{ {
"name": "defuse/php-encryption", "name": "defuse/php-encryption",
@@ -2249,16 +2249,16 @@
}, },
{ {
"name": "filament/actions", "name": "filament/actions",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/actions.git", "url": "https://github.com/filamentphp/actions.git",
"reference": "0eee8f8eeea4422c279c2622442b2a9a101d558c" "reference": "cdefacc18993050cdd37e8e980ec66ca4109ae9a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/filamentphp/actions/zipball/0eee8f8eeea4422c279c2622442b2a9a101d558c", "url": "https://api.github.com/repos/filamentphp/actions/zipball/cdefacc18993050cdd37e8e980ec66ca4109ae9a",
"reference": "0eee8f8eeea4422c279c2622442b2a9a101d558c", "reference": "cdefacc18993050cdd37e8e980ec66ca4109ae9a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2298,20 +2298,20 @@
"issues": "https://github.com/filamentphp/filament/issues", "issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament" "source": "https://github.com/filamentphp/filament"
}, },
"time": "2025-02-06T11:47:20+00:00" "time": "2025-01-31T11:08:24+00:00"
}, },
{ {
"name": "filament/filament", "name": "filament/filament",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/panels.git", "url": "https://github.com/filamentphp/panels.git",
"reference": "407b52ee57feed6f67706af2a18bc81d32aea3f1" "reference": "c92daf4b6e4b478be5d32d5e1b404ba92bb45414"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/filamentphp/panels/zipball/407b52ee57feed6f67706af2a18bc81d32aea3f1", "url": "https://api.github.com/repos/filamentphp/panels/zipball/c92daf4b6e4b478be5d32d5e1b404ba92bb45414",
"reference": "407b52ee57feed6f67706af2a18bc81d32aea3f1", "reference": "c92daf4b6e4b478be5d32d5e1b404ba92bb45414",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2363,20 +2363,20 @@
"issues": "https://github.com/filamentphp/filament/issues", "issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament" "source": "https://github.com/filamentphp/filament"
}, },
"time": "2025-02-06T11:47:25+00:00" "time": "2025-01-31T11:08:29+00:00"
}, },
{ {
"name": "filament/forms", "name": "filament/forms",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/forms.git", "url": "https://github.com/filamentphp/forms.git",
"reference": "6ab0419fd3599aacdd7b2a8f69641cab6f158674" "reference": "8856b3b3714a0efae65d9f817fcc1934b6f34fda"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/filamentphp/forms/zipball/6ab0419fd3599aacdd7b2a8f69641cab6f158674", "url": "https://api.github.com/repos/filamentphp/forms/zipball/8856b3b3714a0efae65d9f817fcc1934b6f34fda",
"reference": "6ab0419fd3599aacdd7b2a8f69641cab6f158674", "reference": "8856b3b3714a0efae65d9f817fcc1934b6f34fda",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2419,11 +2419,11 @@
"issues": "https://github.com/filamentphp/filament/issues", "issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament" "source": "https://github.com/filamentphp/filament"
}, },
"time": "2025-02-06T11:47:21+00:00" "time": "2025-01-31T11:08:23+00:00"
}, },
{ {
"name": "filament/infolists", "name": "filament/infolists",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/infolists.git", "url": "https://github.com/filamentphp/infolists.git",
@@ -2474,7 +2474,7 @@
}, },
{ {
"name": "filament/notifications", "name": "filament/notifications",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/notifications.git", "url": "https://github.com/filamentphp/notifications.git",
@@ -2526,7 +2526,7 @@
}, },
{ {
"name": "filament/support", "name": "filament/support",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/support.git", "url": "https://github.com/filamentphp/support.git",
@@ -2585,16 +2585,16 @@
}, },
{ {
"name": "filament/tables", "name": "filament/tables",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/tables.git", "url": "https://github.com/filamentphp/tables.git",
"reference": "d420b0b0cbe605c712ab054a43d51baf5095dc87" "reference": "1b1d9c3b837c11408ad28240dc9e3e33340d870b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/filamentphp/tables/zipball/d420b0b0cbe605c712ab054a43d51baf5095dc87", "url": "https://api.github.com/repos/filamentphp/tables/zipball/1b1d9c3b837c11408ad28240dc9e3e33340d870b",
"reference": "d420b0b0cbe605c712ab054a43d51baf5095dc87", "reference": "1b1d9c3b837c11408ad28240dc9e3e33340d870b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2633,11 +2633,11 @@
"issues": "https://github.com/filamentphp/filament/issues", "issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament" "source": "https://github.com/filamentphp/filament"
}, },
"time": "2025-02-06T11:47:34+00:00" "time": "2025-01-31T11:08:58+00:00"
}, },
{ {
"name": "filament/widgets", "name": "filament/widgets",
"version": "v3.2.137", "version": "v3.2.136",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/filamentphp/widgets.git", "url": "https://github.com/filamentphp/widgets.git",
@@ -9112,27 +9112,27 @@
}, },
{ {
"name": "spatie/laravel-package-tools", "name": "spatie/laravel-package-tools",
"version": "1.19.0", "version": "1.18.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/laravel-package-tools.git", "url": "https://github.com/spatie/laravel-package-tools.git",
"reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa" "reference": "ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78",
"reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", "reference": "ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/contracts": "^9.28|^10.0|^11.0|^12.0", "illuminate/contracts": "^9.28|^10.0|^11.0",
"php": "^8.0" "php": "^8.0"
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "^1.5", "mockery/mockery": "^1.5",
"orchestra/testbench": "^7.7|^8.0|^9.0|^10.0", "orchestra/testbench": "^7.7|^8.0|^9.0",
"pestphp/pest": "^1.23|^2.1|^3.1", "pestphp/pest": "^1.22|^2",
"phpunit/phpunit": "^9.5.24|^10.5|^11.5", "phpunit/phpunit": "^9.5.24|^10.5",
"spatie/pest-plugin-test-time": "^1.1|^2.2" "spatie/pest-plugin-test-time": "^1.1|^2.2"
}, },
"type": "library", "type": "library",
@@ -9160,7 +9160,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/laravel-package-tools/issues", "issues": "https://github.com/spatie/laravel-package-tools/issues",
"source": "https://github.com/spatie/laravel-package-tools/tree/1.19.0" "source": "https://github.com/spatie/laravel-package-tools/tree/1.18.3"
}, },
"funding": [ "funding": [
{ {
@@ -9168,7 +9168,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-02-06T14:58:20+00:00" "time": "2025-01-22T08:51:18+00:00"
}, },
{ {
"name": "spatie/regex", "name": "spatie/regex",
@@ -14188,16 +14188,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "11.5.7", "version": "11.5.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "e1cb706f019e2547039ca2c839898cd5f557ee5d" "reference": "3c3ae14c90f244cdda95028c3e469028e8d1c02c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e1cb706f019e2547039ca2c839898cd5f557ee5d", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c3ae14c90f244cdda95028c3e469028e8d1c02c",
"reference": "e1cb706f019e2547039ca2c839898cd5f557ee5d", "reference": "3c3ae14c90f244cdda95028c3e469028e8d1c02c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -14269,7 +14269,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.7" "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.6"
}, },
"funding": [ "funding": [
{ {
@@ -14285,7 +14285,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-02-06T16:10:05+00:00" "time": "2025-01-31T07:03:30+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",

View File

@@ -109,7 +109,7 @@ services:
- sail - sail
- reverse-proxy - reverse-proxy
playwright: playwright:
image: mcr.microsoft.com/playwright:v1.50.0-jammy image: mcr.microsoft.com/playwright:v1.46.1-jammy
command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0'] command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0']
working_dir: /src working_dir: /src
extra_hosts: extra_hosts:

View File

@@ -191,7 +191,7 @@ test('test that updating a the start of an existing time entry in the overview w
'time_entry_range_selector' 'time_entry_range_selector'
); );
await timeEntryRangeElement.click(); await timeEntryRangeElement.click();
await page.getByTestId('time_entry_range_start').first().fill('1'); await page.getByTestId('time_picker_input').first().fill('1');
await Promise.all([ await Promise.all([
page.waitForResponse(async (response) => { page.waitForResponse(async (response) => {
return ( return (
@@ -204,7 +204,10 @@ test('test that updating a the start of an existing time entry in the overview w
(await response.json()).data.end !== null (await response.json()).data.end !== null
); );
}), }),
page.getByTestId('time_entry_range_end').press('Enter'), page
.getByTestId('time_entry_range_end')
.getByTestId('time_picker_input')
.press('Enter'),
]); ]);
}); });

View File

@@ -152,7 +152,7 @@ test('test that starting and updating the time while running works', async ({
JSON.stringify([]) JSON.stringify([])
); );
}), }),
page.getByTestId('time_entry_time').press('Enter'), page.getByTestId('time_entry_time').press('Tab'),
]); ]);
await expect(page.getByTestId('time_entry_time')).toHaveValue(/00:20/); await expect(page.getByTestId('time_entry_time')).toHaveValue(/00:20/);

View File

@@ -1,36 +0,0 @@
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
import eslintPluginVue from 'eslint-plugin-vue';
import globals from 'globals';
import typescriptEslint from 'typescript-eslint';
import unusedImports from "eslint-plugin-unused-imports";
export default typescriptEslint.config(
{ ignores: ['*.d.ts', '**/coverage', '**/dist'] },
{
extends: [
eslint.configs.recommended,
...typescriptEslint.configs.recommended,
...eslintPluginVue.configs['flat/recommended'],
],
files: ['**/*.{ts,vue,js}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: globals.browser,
parserOptions: {
parser: typescriptEslint.parser,
},
},
plugins: {
"unused-imports": unusedImports,
},
rules: {
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": "error",
},
},
eslintConfigPrettier
);

View File

@@ -5,20 +5,18 @@ declare(strict_types=1);
return [ return [
'clockify_time_entries' => [ 'clockify_time_entries' => [
'name' => 'Clockify Time Entries', 'name' => 'Clockify Time Entries',
'description' => '1. First make sure that you set the Date format to "MM/DD/YYYY" and the Time format to "12-hour" in the user settings.<br>'. 'description' => '1. First make sure that you set the Date format to "MM/DD/YYYY" and the Time format to "12-hour" in the user settings.<br> '.
'2. In the same preferences page change the language of Clockfiy to English.<br>'. '2. Go to REPORTS -> TIME -> Detailed in the navigation on the left. <br>'.
'3. Go to REPORTS -> TIME -> Detailed in the navigation on the left. <br>'. '3. Now select the date range that you want to export in the right top. '.
'4. Now select the date range that you want to export in the right top. '.
'It is currently not possible to select more than one year. You can export each year separately and import them one after another .'. 'It is currently not possible to select more than one year. You can export each year separately and import them one after another .'.
'<br> 4. Now click Export -> Save as CSV. The Export dropdown is in the header of the export table left of the printer symbol. '. '<br> 4. Now click Export -> Save as CSV. The Export dropdown is in the header of the export table left of the printer symbol. '.
'<br><br>Before you import make sure that the Timezone settings in Clockify are the same as in solidtime.', '<br><br>Before you import make sure that the Timezone settings in Clockify are the same as in solidtime.',
], ],
'clockify_projects' => [ 'clockify_projects' => [
'name' => 'Clockify Projects', 'name' => 'Clockify Projects',
'description' => '1. Make sure to set the language of Clockify to English in "Preferences -> General".<br>'. 'description' => '1. Go to PROJECTS in the navigation on the left.<br> '.
'2. Go to PROJECTS in the navigation on the left.<br> '. '2. Now click on the three dots on the right of the project that you want to export and select Export.<br> '.
'3. Now click on the three dots on the right of the project that you want to export and select Export.<br> '. '3. Now click Export -> Save as CSV. The Export dropdown is in the header of the export table in the top right corner.',
'4. Now click Export -> Save as CSV. The Export dropdown is in the header of the export table in the top right corner.',
], ],
'toggl_data_importer' => [ 'toggl_data_importer' => [
'name' => 'Toggl Data Importer', 'name' => 'Toggl Data Importer',

3414
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,60 +4,53 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"lint": "eslint resources/js", "lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore resources/js",
"lint:fix": "eslint --fix resources/js", "lint:fix": "eslint --fix --ext .js,.vue,.ts --ignore-path .gitignore resources/js",
"type-check": "vue-tsc --noEmit", "type-check": "vue-tsc --noEmit",
"test:e2e": "rm -rf test-results/.auth && npx playwright test", "test:e2e": "rm -rf test-results/.auth && npx playwright test",
"zod:generate": "npx openapi-zod-client http://localhost:80/docs/api.json --output resources/js/packages/api/src/openapi.json.client.ts --base-url /api" "zod:generate": "npx openapi-zod-client http://localhost:80/docs/api.json --output resources/js/packages/api/src/openapi.json.client.ts --base-url /api"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@inertiajs/vue3": "^1.0.0", "@inertiajs/vue3": "^1.0.0",
"@playwright/test": "^1.41.1", "@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/node": "^22.10.10", "@types/node": "^20.11.5",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^4.5.0",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.6.4", "axios": "^1.6.4",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^3.1.0",
"laravel-vite-plugin": "^1.0.0", "laravel-vite-plugin": "^1.0.0",
"openapi-zod-client": "^1.16.2", "openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-nesting": "^12.1.5", "postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"typescript": "^5.7.3", "typescript": "^5.3.3",
"vite": "^6.0.11", "vite": "^5.0.0",
"vite-plugin-checker": "^0.8.0", "vite-plugin-checker": "^0.7.2",
"vue": "^3.5.0", "vue": "^3.4.0",
"vue-tsc": "^2.2.0" "vue-tsc": "^2.0.28"
}, },
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.6.0", "@floating-ui/core": "^1.6.0",
"@floating-ui/vue": "^1.0.6", "@floating-ui/vue": "^1.0.6",
"@heroicons/vue": "^2.1.1", "@heroicons/vue": "^2.1.1",
"@rushstack/eslint-patch": "^1.10.5", "@rushstack/eslint-patch": "^1.7.0",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-query": "^5.56.2", "@tanstack/vue-query": "^5.56.2",
"@tanstack/vue-query-devtools": "^5.58.0", "@tanstack/vue-query-devtools": "^5.58.0",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^14.3.0", "@vue/eslint-config-typescript": "^13.0.0",
"@vueuse/core": "^12.5.0", "@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^12.5.0", "@vueuse/integrations": "^11.1.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"focus-trap": "^7.6.0", "focus-trap": "^7.6.0",
"parse-duration": "^2.0.1", "parse-duration": "^1.1.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"radix-vue": "^1.9.6", "radix-vue": "^1.9.6",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"vue-echarts": "^7.0.3" "vue-echarts": "^6.7.2"
},
"overrides": {
"vite-plugin-checker": {
"vue-tsc": "$vue-tsc"
}
} }
} }

View File

@@ -112,7 +112,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span> <span>Upgrade now</span>
</div> </div>
</Link> </Link>
<button class="p-1" @click="hideBlackFridayBanner = true"> <button @click="hideBlackFridayBanner = true" class="p-1">
<XMarkIcon <XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon> class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button> </button>
@@ -142,7 +142,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span> <span>Upgrade now</span>
</div> </div>
</Link> </Link>
<button class="p-1" @click="hideTrialBanner = true"> <button @click="hideTrialBanner = true" class="p-1">
<XMarkIcon <XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon> class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button> </button>
@@ -174,7 +174,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span> <span>Upgrade now</span>
</div> </div>
</Link> </Link>
<button class="p-1" @click="hideBlockedBanner = true"> <button @click="hideBlockedBanner = true" class="p-1">
<XMarkIcon <XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon> class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button> </button>
@@ -206,7 +206,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span> <span>Upgrade now</span>
</div> </div>
</Link> </Link>
<button class="p-1" @click="hideFreeUpgradeBanner = true"> <button @click="hideFreeUpgradeBanner = true" class="p-1">
<XMarkIcon <XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon> class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button> </button>

View File

@@ -45,10 +45,10 @@ useFocus(clientNameInput, { initialValue: true });
v-model="client.name" v-model="client.name"
type="text" type="text"
placeholder="Client Name" placeholder="Client Name"
@keydown.enter="submit"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="clientName" autocomplete="clientName" />
@keydown.enter="submit" />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -46,10 +46,10 @@ useFocus(clientNameInput, { initialValue: true });
v-model="clientBody.name" v-model="clientBody.name"
type="text" type="text"
placeholder="Client Name" placeholder="Client Name"
@keydown.enter="submit"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="clientName" autocomplete="clientName" />
@keydown.enter="submit" />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -23,28 +23,28 @@ const props = defineProps<{
<div class="min-w-[150px]"> <div class="min-w-[150px]">
<button <button
v-if="canUpdateClients()" v-if="canUpdateClients()"
@click="emit('edit')"
:aria-label="'Edit Client ' + props.client.name" :aria-label="'Edit Client ' + props.client.name"
data-testid="client_edit" data-testid="client_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('edit')">
<PencilSquareIcon <PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon> class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
@click.prevent="emit('archive')"
v-if="canUpdateClients()" v-if="canUpdateClients()"
:aria-label="'Archive Client ' + props.client.name" :aria-label="'Archive Client ' + props.client.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('archive')">
<ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon> <ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon>
<span>{{ client.is_archived ? 'Unarchive' : 'Archive' }}</span> <span>{{ client.is_archived ? 'Unarchive' : 'Archive' }}</span>
</button> </button>
<button <button
v-if="canDeleteClients()" v-if="canDeleteClients()"
@click="emit('delete')"
:aria-label="'Delete Client ' + props.client.name" :aria-label="'Delete Client ' + props.client.name"
data-testid="client_delete" data-testid="client_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Client) {
<template> <template>
<MultiselectDropdown <MultiselectDropdown
search-placeholder="Search for a Client..." searchPlaceholder="Search for a Client..."
:items="clients" :items="clients"
:get-key-from-item="getKeyFromItem" :get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem"> :get-name-for-item="getNameForItem">

View File

@@ -25,18 +25,18 @@ const createClient = ref(false);
style="grid-template-columns: 1fr 150px 200px 80px"> style="grid-template-columns: 1fr 150px 200px 80px">
<ClientTableHeading></ClientTableHeading> <ClientTableHeading></ClientTableHeading>
<div <div
v-if="clients.length === 0" class="col-span-2 py-24 text-center"
class="col-span-2 py-24 text-center"> v-if="clients.length === 0">
<UserCircleIcon <UserCircleIcon
class="w-8 text-icon-default inline pb-2"></UserCircleIcon> class="w-8 text-icon-default inline pb-2"></UserCircleIcon>
<h3 class="text-white font-semibold">No clients found</h3> <h3 class="text-white font-semibold">No clients found</h3>
<p v-if="canCreateClients()" class="pb-5"> <p class="pb-5" v-if="canCreateClients()">
Create your first client now! Create your first client now!
</p> </p>
<SecondaryButton <SecondaryButton
v-if="canCreateClients()" v-if="canCreateClients()"
:icon="PlusIcon as Component"
@click="createClient = true" @click="createClient = true"
:icon="PlusIcon as Component"
>Create your First Client >Create your First Client
</SecondaryButton> </SecondaryButton>
</div> </div>

View File

@@ -38,8 +38,8 @@ const showEditModal = ref(false);
<template> <template>
<TableRow> <TableRow>
<ClientEditModal <ClientEditModal
v-model:show="showEditModal" :client="client"
:client="client"></ClientEditModal> v-model:show="showEditModal"></ClientEditModal>
<div <div
class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12"> class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12">
<span> <span>

View File

@@ -10,16 +10,16 @@ const emit = defineEmits<{
<template> <template>
<MoreOptionsDropdown label="Actions for the invitation"> <MoreOptionsDropdown label="Actions for the invitation">
<button <button
@click="emit('resend')"
data-testid="invitation_delete" data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('resend')">
<ArrowPathIcon class="w-5 text-icon-active"></ArrowPathIcon> <ArrowPathIcon class="w-5 text-icon-active"></ArrowPathIcon>
<span>Resend Invitation</span> <span>Resend Invitation</span>
</button> </button>
<button <button
@click="emit('delete')"
data-testid="invitation_delete" data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -18,10 +18,10 @@ defineEmits<{
<template> <template>
<BillableRateModal <BillableRateModal
@submit="$emit('submit')"
v-model:show="show" v-model:show="show"
v-model:saving="saving" v-model:saving="saving"
title="Update Member Billable Rate" title="Update Member Billable Rate">
@submit="$emit('submit')">
<p class="py-1 text-center"> <p class="py-1 text-center">
The billable rate of {{ memberName }} will be updated to The billable rate of {{ memberName }} will be updated to
<strong>{{ <strong>{{

View File

@@ -17,8 +17,8 @@ const model = defineModel<string>({
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
hiddenMembers?: ProjectMember[]; hiddenMembers: ProjectMember[];
disabled?: boolean; disabled: boolean;
}>(), }>(),
{ {
hiddenMembers: () => [] as ProjectMember[], hiddenMembers: () => [] as ProjectMember[],
@@ -76,7 +76,7 @@ const currentValue = computed(() => {
:items="filteredMembers" :items="filteredMembers"
:get-key-from-item="(member) => member.id" :get-key-from-item="(member) => member.id"
:get-name-for-item="(member) => member.name"> :get-name-for-item="(member) => member.name">
<template #trigger> <template v-slot:trigger>
<Badge <Badge
tag="button" tag="button"
class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5"> class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
@@ -84,7 +84,7 @@ const currentValue = computed(() => {
<div v-if="currentValue" class="flex-1 truncate"> <div v-if="currentValue" class="flex-1 truncate">
{{ currentValue }} {{ currentValue }}
</div> </div>
<div v-else class="flex-1">Select a member...</div> <div class="flex-1" v-else>Select a member...</div>
<ChevronDownIcon class="w-4 text-muted"></ChevronDownIcon> <ChevronDownIcon class="w-4 text-muted"></ChevronDownIcon>
</Badge> </Badge>
</template> </template>

View File

@@ -108,11 +108,11 @@ const roleDescription = computed(() => {
v-model:saving="saving" v-model:saving="saving"
v-model:show="showBillableRateModal" v-model:show="showBillableRateModal"
:member-name="member.name" :member-name="member.name"
:new-billable-rate="memberBody.billable_rate" :newBillableRate="memberBody.billable_rate"
@submit="submitBillableRate"></MemberBillableRateModal> @submit="submitBillableRate"></MemberBillableRateModal>
<MemberOwnershipTransferConfirmModal <MemberOwnershipTransferConfirmModal
v-model:show="showOwnershipTransferConfirmModal"
:member-name="member.name" :member-name="member.name"
v-model:show="showOwnershipTransferConfirmModal"
@submit="submit"></MemberOwnershipTransferConfirmModal> @submit="submit"></MemberOwnershipTransferConfirmModal>
<DialogModal closeable :show="show" @close="show = false"> <DialogModal closeable :show="show" @close="show = false">
<template #title> <template #title>
@@ -127,9 +127,9 @@ const roleDescription = computed(() => {
<div> <div>
<InputLabel for="role" value="Role" /> <InputLabel for="role" value="Role" />
<MemberRoleSelect <MemberRoleSelect
v-model="memberBody.role"
class="mt-2" class="mt-2"
name="role"></MemberRoleSelect> name="role"
v-model="memberBody.role"></MemberRoleSelect>
</div> </div>
<div class="flex-1 text-xs flex items-center pt-6"> <div class="flex-1 text-xs flex items-center pt-6">
<p>{{ roleDescription }}</p> <p>{{ roleDescription }}</p>
@@ -140,28 +140,28 @@ const roleDescription = computed(() => {
<div> <div>
<InputLabel for="billableType" value="Billable" /> <InputLabel for="billableType" value="Billable" />
<MemberBillableSelect <MemberBillableSelect
class="mt-2"
name="billableType"
v-model=" v-model="
billableRateSelect billableRateSelect
" "></MemberBillableSelect>
class="mt-2"
name="billableType"></MemberBillableSelect>
</div> </div>
<div <div
v-if="billableRateSelect === 'custom-rate'" class="flex-1"
class="flex-1"> v-if="billableRateSelect === 'custom-rate'">
<InputLabel <InputLabel
for="memberBillableRate" for="memberBillableRate"
class="mb-2" class="mb-2"
value="Billable Rate" /> value="Billable Rate" />
<BillableRateInput <BillableRateInput
v-model="
memberBody.billable_rate
"
focus focus
class="w-full" class="w-full"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
@keydown.enter="saveWithChecks()"
name="memberBillableRate" name="memberBillableRate"
@keydown.enter="saveWithChecks()"></BillableRateInput> v-model="
memberBody.billable_rate
"></BillableRateInput>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -112,11 +112,11 @@ useFocus(clientNameInput, { initialValue: true });
v-if="isBillingActivated() && canManageBilling()" v-if="isBillingActivated() && canManageBilling()"
href="/billing"> href="/billing">
<PrimaryButton <PrimaryButton
type="button"
class="mt-6"
v-if=" v-if="
isBillingActivated() && canUpdateOrganization() isBillingActivated() && canUpdateOrganization()
" ">
type="button"
class="mt-6">
<CreditCardIcon class="w-5 h-5 me-2" /> <CreditCardIcon class="w-5 h-5 me-2" />
Go to Billing Go to Billing
</PrimaryButton> </PrimaryButton>
@@ -128,15 +128,15 @@ useFocus(clientNameInput, { initialValue: true });
<InputLabel for="email" value="Email" /> <InputLabel for="email" value="Email" />
<TextInput <TextInput
id="email" id="email"
name="email"
ref="memberEmailInput" ref="memberEmailInput"
v-model="addTeamMemberForm.email" v-model="addTeamMemberForm.email"
name="email"
type="text" type="text"
placeholder="Member Email" placeholder="Member Email"
@keydown.enter="submit"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="memberName" autocomplete="memberName" />
@keydown.enter="submit" />
<InputError :message="errors.email" class="mt-2" /> <InputError :message="errors.email" class="mt-2" />
</div> </div>

View File

@@ -20,19 +20,19 @@ const props = defineProps<{
<div class="min-w-[150px]"> <div class="min-w-[150px]">
<button <button
v-if="canUpdateMembers()" v-if="canUpdateMembers()"
@click="emit('edit')"
:aria-label="'Edit Member ' + props.member.name" :aria-label="'Edit Member ' + props.member.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('edit')">
<PencilSquareIcon <PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon> class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
v-if="canDeleteMembers()" v-if="canDeleteMembers()"
@click="emit('delete')"
:aria-label="'Delete Member ' + props.member.name" :aria-label="'Delete Member ' + props.member.name"
data-testid="member_delete" data-testid="member_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Member) {
<template> <template>
<MultiselectDropdown <MultiselectDropdown
search-placeholder="Search for a Member..." searchPlaceholder="Search for a Member..."
:items="members" :items="members"
:get-key-from-item="getKeyFromItem" :get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem"> :get-name-for-item="getNameForItem">

View File

@@ -36,9 +36,9 @@ const emit = defineEmits<{
<SecondaryButton @click="show = false"> Cancel</SecondaryButton> <SecondaryButton @click="show = false"> Cancel</SecondaryButton>
<PrimaryButton <PrimaryButton
class="ms-3" class="ms-3"
@click="emit('submit')"
:class="{ 'opacity-25': saving }" :class="{ 'opacity-25': saving }"
:disabled="saving" :disabled="saving">
@click="emit('submit')">
Confirm Transfer Confirm Transfer
</PrimaryButton> </PrimaryButton>
</template> </template>

View File

@@ -89,8 +89,8 @@ async function invitePlaceholder(id: string) {
member.is_placeholder === true && member.is_placeholder === true &&
canInvitePlaceholderMembers() canInvitePlaceholderMembers()
" "
size="small"
@click="invitePlaceholder(member.id)" @click="invitePlaceholder(member.id)"
size="small"
>Invite</SecondaryButton >Invite</SecondaryButton
> >
<MemberMoreOptionsDropdown <MemberMoreOptionsDropdown
@@ -99,8 +99,8 @@ async function invitePlaceholder(id: string) {
@delete="removeMember"></MemberMoreOptionsDropdown> @delete="removeMember"></MemberMoreOptionsDropdown>
</div> </div>
<MemberEditModal <MemberEditModal
v-model:show="showEditMemberModal" :member="member"
:member="member"></MemberEditModal> v-model:show="showEditMemberModal"></MemberEditModal>
</TableRow> </TableRow>
</template> </template>

View File

@@ -34,8 +34,8 @@
<div class="ml-4 flex flex-shrink-0"> <div class="ml-4 flex flex-shrink-0">
<button <button
type="button" type="button"
class="inline-flex rounded-md bg-card-background text-muted hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" @click="show = false"
@click="show = false"> class="inline-flex rounded-md bg-card-background text-muted hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" /> <XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button> </button>

View File

@@ -17,10 +17,10 @@ defineEmits<{
<template> <template>
<BillableRateModal <BillableRateModal
@submit="$emit('submit')"
v-model:show="show" v-model:show="show"
v-model:saving="saving" v-model:saving="saving"
title="Update Organization Billable Rate" title="Update Organization Billable Rate">
@submit="$emit('submit')">
<p class="py-0.5 text-center"> <p class="py-0.5 text-center">
The organization billable rate will be updated to The organization billable rate will be updated to
<strong>{{ <strong>{{

View File

@@ -40,7 +40,7 @@ const shownProjects = computed(() => {
withDefaults( withDefaults(
defineProps<{ defineProps<{
border?: boolean; border: boolean;
}>(), }>(),
{ {
border: true, border: true,
@@ -123,17 +123,17 @@ function updateValue(project: Project) {
<template #content> <template #content>
<ComboboxRoot <ComboboxRoot
:open="open" :open="open"
:model-value="currentProject" :modelValue="currentProject"
:search-term="searchValue" @update:modelValue="updateValue"
class="relative" @update:searchTerm="(e) => console.log(e)"
@update:model-value="updateValue" :searchTerm="searchValue"
@update:search-term="(e) => console.log(e)"> class="relative">
<ComboboxAnchor> <ComboboxAnchor>
<ComboboxInput <ComboboxInput
@keydown.enter="addProjectIfNoneExists"
ref="searchInput" ref="searchInput"
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full" class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
placeholder="Search for a project..." placeholder="Search for a project..." />
@keydown.enter="addProjectIfNoneExists" />
</ComboboxAnchor> </ComboboxAnchor>
<ComboboxContent> <ComboboxContent>
<ComboboxViewport <ComboboxViewport

View File

@@ -90,8 +90,8 @@ async function submitBillableRate() {
<div class="text-center"> <div class="text-center">
<InputLabel for="color" value="Color" /> <InputLabel for="color" value="Color" />
<ProjectColorSelector <ProjectColorSelector
v-model="project.color" class="mt-1"
class="mt-1"></ProjectColorSelector> v-model="project.color"></ProjectColorSelector>
</div> </div>
</div> </div>
<div class="w-full"> <div class="w-full">
@@ -102,18 +102,18 @@ async function submitBillableRate() {
v-model="project.name" v-model="project.name"
type="text" type="text"
placeholder="Project Name" placeholder="Project Name"
@keydown.enter="submit()"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="projectName" autocomplete="projectName" />
@keydown.enter="submit()" />
</div> </div>
<div class=""> <div class="">
<InputLabel for="client" value="Client" /> <InputLabel for="client" value="Client" />
<ClientDropdown <ClientDropdown
v-model="project.client_id" :createClient
:create-client
:clients="clients" :clients="clients"
class="mt-1"> class="mt-1"
v-model="project.client_id">
<template #trigger> <template #trigger>
<Badge <Badge
class="bg-input-background cursor-pointer hover:bg-tertiary" class="bg-input-background cursor-pointer hover:bg-tertiary"
@@ -133,18 +133,18 @@ async function submitBillableRate() {
<div class="lg:grid grid-cols-2 gap-12"> <div class="lg:grid grid-cols-2 gap-12">
<div> <div>
<ProjectEditBillableSection <ProjectEditBillableSection
v-model:is-billable="project.is_billable" @submit="submit"
v-model:billable-rate="
project.billable_rate
"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
@submit="submit"></ProjectEditBillableSection> v-model:isBillable="project.is_billable"
v-model:billableRate="
project.billable_rate
"></ProjectEditBillableSection>
</div> </div>
<div> <div>
<EstimatedTimeSection <EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()" v-if="isAllowedToPerformPremiumAction()"
v-model="project.estimated_time" @submit="submit()"
@submit="submit()"></EstimatedTimeSection> v-model="project.estimated_time"></EstimatedTimeSection>
</div> </div>
</div> </div>
</template> </template>
@@ -163,9 +163,9 @@ async function submitBillableRate() {
<ProjectBillableRateModal <ProjectBillableRateModal
v-model:show="showBillableRateModal" v-model:show="showBillableRateModal"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
@submit="submitBillableRate"
:new-billable-rate="project.billable_rate" :new-billable-rate="project.billable_rate"
:project-name="project.name" :project-name="project.name"></ProjectBillableRateModal>
@submit="submitBillableRate"></ProjectBillableRateModal>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -21,29 +21,29 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Project ' + props.project.name"> <MoreOptionsDropdown :label="'Actions for Project ' + props.project.name">
<div class="min-w-[150px]"> <div class="min-w-[150px]">
<button <button
@click.prevent="emit('edit')"
v-if="canUpdateProjects()" v-if="canUpdateProjects()"
:aria-label="'Edit Project ' + props.project.name" :aria-label="'Edit Project ' + props.project.name"
data-testid="project_edit" data-testid="project_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('edit')">
<PencilSquareIcon <PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon> class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
@click.prevent="emit('archive')"
v-if="canUpdateProjects()" v-if="canUpdateProjects()"
:aria-label="'Archive Project ' + props.project.name" :aria-label="'Archive Project ' + props.project.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('archive')">
<ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon> <ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon>
<span>{{ project.is_archived ? 'Unarchive' : 'Archive' }}</span> <span>{{ project.is_archived ? 'Unarchive' : 'Archive' }}</span>
</button> </button>
<button <button
v-if="canDeleteProjects()" @click.prevent="emit('delete')"
:aria-label="'Delete Project ' + props.project.name" :aria-label="'Delete Project ' + props.project.name"
data-testid="project_delete" data-testid="project_delete"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" v-if="canDeleteProjects()"
@click.prevent="emit('delete')"> class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Project) {
<template> <template>
<MultiselectDropdown <MultiselectDropdown
search-placeholder="Search for a Project..." searchPlaceholder="Search for a Project..."
:items="projects" :items="projects"
:get-key-from-item="getKeyFromItem" :get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem"> :get-name-for-item="getNameForItem">

View File

@@ -44,12 +44,12 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
<template> <template>
<ProjectCreateModal <ProjectCreateModal
v-model:show="showCreateProjectModal" :createProject
:create-project :createClient
:create-client
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
:clients="clients" :clients="clients"
:enable-estimated-time="isAllowedToPerformPremiumAction"></ProjectCreateModal> :enableEstimatedTime="isAllowedToPerformPremiumAction"
v-model:show="showCreateProjectModal"></ProjectCreateModal>
<div class="flow-root max-w-[100vw] overflow-x-auto"> <div class="flow-root max-w-[100vw] overflow-x-auto">
<div class="inline-block min-w-full align-middle"> <div class="inline-block min-w-full align-middle">
<div <div
@@ -57,12 +57,12 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
class="grid min-w-full" class="grid min-w-full"
:style="gridTemplate"> :style="gridTemplate">
<ProjectTableHeading <ProjectTableHeading
:show-billable-rate=" :showBillableRate="
props.showBillableRate props.showBillableRate
"></ProjectTableHeading> "></ProjectTableHeading>
<div <div
v-if="projects.length === 0" class="col-span-5 py-24 text-center"
class="col-span-5 py-24 text-center"> v-if="projects.length === 0">
<FolderPlusIcon <FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon> class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold"> <h3 class="text-white font-semibold">
@@ -81,14 +81,14 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
</p> </p>
<SecondaryButton <SecondaryButton
v-if="canCreateProjects()" v-if="canCreateProjects()"
:icon="PlusIcon"
@click="showCreateProjectModal = true" @click="showCreateProjectModal = true"
:icon="PlusIcon"
>Create your First Project >Create your First Project
</SecondaryButton> </SecondaryButton>
</div> </div>
<template v-for="project in projects" :key="project.id"> <template v-for="project in projects" :key="project.id">
<ProjectTableRow <ProjectTableRow
:show-billable-rate="props.showBillableRate" :showBillableRate="props.showBillableRate"
:project="project"></ProjectTableRow> :project="project"></ProjectTableRow>
</template> </template>
</div> </div>

View File

@@ -19,8 +19,8 @@ defineProps<{
Progress Progress
</div> </div>
<div <div
v-if="showBillableRate" class="px-3 py-1.5 text-left font-semibold text-white"
class="px-3 py-1.5 text-left font-semibold text-white"> v-if="showBillableRate">
Billable Rate Billable Rate
</div> </div>
<div class="px-3 py-1.5 text-left font-semibold text-white">Status</div> <div class="px-3 py-1.5 text-left font-semibold text-white">Status</div>

View File

@@ -83,8 +83,8 @@ const showEditProjectModal = ref(false);
</div> </div>
<div class="whitespace-nowrap min-w-0 px-3 py-4 text-sm text-muted"> <div class="whitespace-nowrap min-w-0 px-3 py-4 text-sm text-muted">
<div <div
v-if="project.client_id" class="overflow-ellipsis overflow-hidden"
class="overflow-ellipsis overflow-hidden"> v-if="project.client_id">
{{ client?.name }} {{ client?.name }}
</div> </div>
<div v-else>No client</div> <div v-else>No client</div>
@@ -106,8 +106,8 @@ const showEditProjectModal = ref(false);
<span v-else> -- </span> <span v-else> -- </span>
</div> </div>
<div <div
v-if="showBillableRate" class="whitespace-nowrap px-3 py-4 text-sm text-muted"
class="whitespace-nowrap px-3 py-4 text-sm text-muted"> v-if="showBillableRate">
{{ billableRateInfo }} {{ billableRateInfo }}
</div> </div>
<div <div

View File

@@ -18,10 +18,10 @@ defineEmits<{
<template> <template>
<BillableRateModal <BillableRateModal
@submit="$emit('submit')"
v-model:show="show" v-model:show="show"
v-model:saving="saving" v-model:saving="saving"
title="Update Project Member Billable Rate" title="Update Project Member Billable Rate">
@submit="$emit('submit')">
<p class="py-1 text-center"> <p class="py-1 text-center">
The billable rate of {{ memberName }} will be updated to The billable rate of {{ memberName }} will be updated to
<strong>{{ <strong>{{

View File

@@ -52,16 +52,16 @@ useFocus(projectNameInput, { initialValue: true });
<div class="grid grid-cols-3 items-center space-x-4"> <div class="grid grid-cols-3 items-center space-x-4">
<div class="col-span-3 sm:col-span-2"> <div class="col-span-3 sm:col-span-2">
<MemberCombobox <MemberCombobox
v-model="projectMember.member_id" :hidden-members="props.existingMembers"
:hidden-members="props.existingMembers"></MemberCombobox> v-model="projectMember.member_id"></MemberCombobox>
</div> </div>
<div class="col-span-3 sm:col-span-1 flex-1"> <div class="col-span-3 sm:col-span-1 flex-1">
<BillableRateInput <BillableRateInput
name="billable_rate"
:currency="getOrganizationCurrencyString()"
v-model=" v-model="
projectMember.billable_rate projectMember.billable_rate
" "></BillableRateInput>
name="billable_rate"
:currency="getOrganizationCurrencyString()"></BillableRateInput>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -75,8 +75,8 @@ useFocus(projectNameInput, { initialValue: true });
<template #content> <template #content>
<ProjectMemberBillableRateModal <ProjectMemberBillableRateModal
v-model:show="showBillableRateModal"
:member-name="props.name" :member-name="props.name"
v-model:show="showBillableRateModal"
:new-billable-rate="projectMemberBody.billable_rate" :new-billable-rate="projectMemberBody.billable_rate"
@close="showBillableRateModal = false" @close="showBillableRateModal = false"
@submit="submitBillableRate"></ProjectMemberBillableRateModal> @submit="submitBillableRate"></ProjectMemberBillableRateModal>
@@ -92,12 +92,12 @@ useFocus(projectNameInput, { initialValue: true });
class="mb-2" class="mb-2"
value="Billable Rate"></InputLabel> value="Billable Rate"></InputLabel>
<BillableRateInput <BillableRateInput
v-model=" @keydown.enter="submit"
projectMemberBody.billable_rate
"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
name="billable_rate" name="billable_rate"
@keydown.enter="submit"></BillableRateInput> v-model="
projectMemberBody.billable_rate
"></BillableRateInput>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -27,17 +27,17 @@ const currentMember = computed(() => {
<MoreOptionsDropdown <MoreOptionsDropdown
:label="'Actions for Project Member ' + currentMember?.name"> :label="'Actions for Project Member ' + currentMember?.name">
<button <button
@click.prevent="emit('edit')"
:aria-label="'Edit Project Member ' + currentMember?.name" :aria-label="'Edit Project Member ' + currentMember?.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('edit')">
<PencilSquareIcon class="w-5 text-icon-active"></PencilSquareIcon> <PencilSquareIcon class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
@click.prevent="emit('delete')"
:aria-label="'Delete Project Member ' + currentMember?.name" :aria-label="'Delete Project Member ' + currentMember?.name"
data-testid="project_delete" data-testid="project_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Remove from Team</span> <span>Remove from Team</span>
</button> </button>

View File

@@ -18,9 +18,9 @@ const createProjectMember = ref(false);
<template> <template>
<ProjectMemberCreateModal <ProjectMemberCreateModal
v-model:show="createProjectMember"
:existing-members="projectMembers" :existing-members="projectMembers"
:project-id="projectId"></ProjectMemberCreateModal> :project-id="projectId"
v-model:show="createProjectMember"></ProjectMemberCreateModal>
<div class="flow-root"> <div class="flow-root">
<div class="inline-block min-w-full align-middle"> <div class="inline-block min-w-full align-middle">
<div <div
@@ -29,15 +29,15 @@ const createProjectMember = ref(false);
style="grid-template-columns: 1fr 150px 150px 80px"> style="grid-template-columns: 1fr 150px 150px 80px">
<ProjectMemberTableHeading></ProjectMemberTableHeading> <ProjectMemberTableHeading></ProjectMemberTableHeading>
<div <div
v-if="projectMembers.length === 0" class="col-span-5 py-24 text-center"
class="col-span-5 py-24 text-center"> v-if="projectMembers.length === 0">
<UserGroupIcon <UserGroupIcon
class="w-8 text-icon-default inline pb-2"></UserGroupIcon> class="w-8 text-icon-default inline pb-2"></UserGroupIcon>
<h3 class="text-white font-semibold">No project members</h3> <h3 class="text-white font-semibold">No project members</h3>
<p class="pb-5">Add the first project member!</p> <p class="pb-5">Add the first project member!</p>
<SecondaryButton <SecondaryButton
:icon="PlusIcon"
@click="createProjectMember = true" @click="createProjectMember = true"
:icon="PlusIcon"
>Add a new Project Member >Add a new Project Member
</SecondaryButton> </SecondaryButton>
</div> </div>

View File

@@ -37,8 +37,8 @@ const showEditModal = ref(false);
<template> <template>
<TableRow> <TableRow>
<ProjectMemberEditModal <ProjectMemberEditModal
v-model:show="showEditModal"
:name="member?.name" :name="member?.name"
v-model:show="showEditModal"
:project-member="projectMember"></ProjectMemberEditModal> :project-member="projectMember"></ProjectMemberEditModal>
<div <div
class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12"> class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12">

View File

@@ -82,22 +82,22 @@ async function submit() {
<InputLabel for="name" value="Name" /> <InputLabel for="name" value="Name" />
<TextInput <TextInput
id="name" id="name"
v-model="report.name" class="mt-1.5 w-full"
class="mt-1.5 w-full"></TextInput> v-model="report.name"></TextInput>
</div> </div>
<div> <div>
<InputLabel for="description" value="Description" /> <InputLabel for="description" value="Description" />
<TextInput <TextInput
id="description" id="description"
v-model="report.description" class="mt-1.5 w-full"
class="mt-1.5 w-full"></TextInput> v-model="report.description"></TextInput>
</div> </div>
<InputLabel value="Visibility" /> <InputLabel value="Visibility" />
<div class="flex items-center space-x-12"> <div class="flex items-center space-x-12">
<div class="flex items-center space-x-3 px-2 py-3"> <div class="flex items-center space-x-3 px-2 py-3">
<Checkbox <Checkbox
id="is_public" v-model:checked="report.is_public"
v-model:checked="report.is_public"></Checkbox> id="is_public"></Checkbox>
<InputLabel for="is_public" value="Public" /> <InputLabel for="is_public" value="Public" />
</div> </div>
<div <div

View File

@@ -96,22 +96,22 @@ async function submit() {
<InputLabel for="name" value="Name" /> <InputLabel for="name" value="Name" />
<TextInput <TextInput
id="name" id="name"
v-model="report.name" class="mt-1.5 w-full"
class="mt-1.5 w-full"></TextInput> v-model="report.name"></TextInput>
</div> </div>
<div> <div>
<InputLabel for="description" value="Description" /> <InputLabel for="description" value="Description" />
<TextInput <TextInput
id="description" id="description"
v-model="report.description" class="mt-1.5 w-full"
class="mt-1.5 w-full"></TextInput> v-model="report.description"></TextInput>
</div> </div>
<InputLabel value="Visibility" /> <InputLabel value="Visibility" />
<div class="flex items-center space-x-12"> <div class="flex items-center space-x-12">
<div class="flex items-center space-x-2 px-2 py-3"> <div class="flex items-center space-x-2 px-2 py-3">
<Checkbox <Checkbox
id="is_public" v-model:checked="report.is_public"
v-model:checked="report.is_public"></Checkbox> id="is_public"></Checkbox>
<InputLabel for="is_public" value="Public" /> <InputLabel for="is_public" value="Public" />
</div> </div>
<div <div

View File

@@ -17,19 +17,19 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Project ' + props.report.name"> <MoreOptionsDropdown :label="'Actions for Project ' + props.report.name">
<div class="min-w-[150px]"> <div class="min-w-[150px]">
<button <button
@click.prevent="emit('edit')"
v-if="canUpdateReport()" v-if="canUpdateReport()"
:aria-label="'Edit Report ' + props.report.name" :aria-label="'Edit Report ' + props.report.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('edit')">
<PencilSquareIcon <PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon> class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
v-if="canDeleteReport()" @click.prevent="emit('delete')"
:aria-label="'Delete Report ' + props.report.name" :aria-label="'Delete Report ' + props.report.name"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" v-if="canDeleteReport()"
@click.prevent="emit('delete')"> class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -27,8 +27,8 @@ function onSaveReportClick() {
<template> <template>
<ReportCreateModal <ReportCreateModal
v-model:show="showCreateReportModal" :properties="reportProperties"
:properties="reportProperties"></ReportCreateModal> v-model:show="showCreateReportModal"></ReportCreateModal>
<UpgradeModal v-model:show="showPremiumModal"> <UpgradeModal v-model:show="showPremiumModal">
<strong>Sharable Reports</strong> is only available in solidtime <strong>Sharable Reports</strong> is only available in solidtime
Professional. Professional.

View File

@@ -27,19 +27,19 @@ const gridTemplate = computed(() => {
:style="gridTemplate"> :style="gridTemplate">
<ReportTableHeading></ReportTableHeading> <ReportTableHeading></ReportTableHeading>
<div <div
v-if="reports.length === 0" class="col-span-5 py-24 text-center"
class="col-span-5 py-24 text-center"> v-if="reports.length === 0">
<FolderPlusIcon <FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon> class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold"> <h3 class="text-white font-semibold">
No shared reports found No shared reports found
</h3> </h3>
<p v-if="canCreateProjects()" class="pb-5"> <p class="pb-5" v-if="canCreateProjects()">
Create your first project now! Create your first project now!
</p> </p>
<SecondaryButton <SecondaryButton
:icon="PlusIcon"
@click="router.visit(route('reporting'))" @click="router.visit(route('reporting'))"
:icon="PlusIcon"
>Go to the overview to create a report >Go to the overview to create a report
</SecondaryButton> </SecondaryButton>
</div> </div>

View File

@@ -87,7 +87,7 @@ async function deleteReport() {
<span v-else>Copied!</span> <span v-else>Copied!</span>
</SecondaryButton> </SecondaryButton>
<button <button
class="outline-0 focus-visible:ring-2 w-6 h-6 flex items-center justify-center rounded focus-visible:ring-ring" class="outline-0 focus-visible:ring-2 w-6 h-6 flex items-center justify-center rounded focus-visible:ring-white/80"
@click="openSharableLink"> @click="openSharableLink">
<ArrowTopRightOnSquareIcon <ArrowTopRightOnSquareIcon
class="w-4 text-text-tertiary hover:text-text-secondary transition"></ArrowTopRightOnSquareIcon> class="w-4 text-text-tertiary hover:text-text-secondary transition"></ArrowTopRightOnSquareIcon>

View File

@@ -157,7 +157,7 @@ const option = ref({
:autoresize="true" :autoresize="true"
class="chart" class="chart"
:option="option" /> :option="option" />
<div v-else class="chart flex flex-col items-center justify-center"> <div class="chart flex flex-col items-center justify-center" v-else>
<p class="text-lg text-white font-semibold"> <p class="text-lg text-white font-semibold">
No time entries found No time entries found
</p> </p>

View File

@@ -22,10 +22,10 @@ function downloadCurrentExport() {
<Modal <Modal
closeable closeable
max-width="lg" max-width="lg"
:show="showExportModal" @close="showExportModal = false"
@close="showExportModal = false"> :show="showExportModal">
<button <button
class="text-text-tertiary w-6 mx-auto absolute focus-visible:outline-none focus-visible:ring-2 rounded-full focus-visible:ring-ring transition focus-visible:text-text-primary hover:text-text-primary top-2 right-2"> class="text-text-tertiary w-6 mx-auto absolute focus-visible:outline-none focus-visible:ring-2 rounded-full focus-visible:ring-white/80 transition focus-visible:text-text-primary hover:text-text-primary top-2 right-2">
<XMarkIcon @click="showExportModal = false"></XMarkIcon> <XMarkIcon @click="showExportModal = false"></XMarkIcon>
</button> </button>
<div class="text-center text-text-primary py-6"> <div class="text-center text-text-primary py-6">

View File

@@ -21,7 +21,6 @@ const activeClass = computed(() => {
<template> <template>
<Badge <Badge
size="large" size="large"
tag="button"
:class=" :class="
twMerge( twMerge(
'cursor-pointer hover:bg-card-background transition flex', 'cursor-pointer hover:bg-card-background transition flex',

View File

@@ -23,7 +23,7 @@ const title = computed(() => {
:get-key-from-item="(item) => item.value" :get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label" :get-name-for-item="(item) => item.label"
:items="groupByOptions"> :items="groupByOptions">
<template #trigger> <template v-slot:trigger>
<Badge <Badge
size="large" size="large"
class="cursor-pointer hover:bg-card-background transition space-x-5 flex"> class="cursor-pointer hover:bg-card-background transition space-x-5 flex">

View File

@@ -35,9 +35,9 @@ const expanded = ref(false);
) )
"> ">
<GroupedItemsCountButton <GroupedItemsCountButton
v-if="entry.grouped_data && entry.grouped_data?.length > 0"
:expanded="expanded" :expanded="expanded"
@click="expanded = !expanded"> @click="expanded = !expanded"
v-if="entry.grouped_data && entry.grouped_data?.length > 0">
{{ entry.grouped_data?.length }} {{ entry.grouped_data?.length }}
</GroupedItemsCountButton> </GroupedItemsCountButton>
<span> <span>
@@ -52,13 +52,13 @@ const expanded = ref(false);
</div> </div>
</div> </div>
<div <div
v-if="expanded && entry.grouped_data"
class="col-span-3 grid bg-quaternary" class="col-span-3 grid bg-quaternary"
style="grid-template-columns: 1fr 150px 150px"> style="grid-template-columns: 1fr 150px 150px"
v-if="expanded && entry.grouped_data">
<ReportingRow <ReportingRow
indent
v-for="subEntry in entry.grouped_data" v-for="subEntry in entry.grouped_data"
:key="subEntry.description ?? 'none'" :key="subEntry.description ?? 'none'"
indent
:entry="subEntry"></ReportingRow> :entry="subEntry"></ReportingRow>
</div> </div>
</template> </template>

View File

@@ -10,18 +10,18 @@ defineProps<{
<template> <template>
<TabBar> <TabBar>
<TabBarItem <TabBarItem
:active="active === 'reporting'"
@click="router.visit(route('reporting'))" @click="router.visit(route('reporting'))"
:active="active === 'reporting'"
>Overview</TabBarItem >Overview</TabBarItem
> >
<TabBarItem <TabBarItem
:active="active === 'detailed'"
@click="router.visit(route('reporting.detailed'))" @click="router.visit(route('reporting.detailed'))"
:active="active === 'detailed'"
>Detailed</TabBarItem >Detailed</TabBarItem
> >
<TabBarItem <TabBarItem
:active="active === 'shared'"
@click="router.visit(route('reporting.shared'))" @click="router.visit(route('reporting.shared'))"
:active="active === 'shared'"
>Shared</TabBarItem >Shared</TabBarItem
> >
</TabBar> </TabBar>

View File

@@ -14,10 +14,10 @@ const props = defineProps<{
<template> <template>
<MoreOptionsDropdown :label="'Actions for Tag ' + props.tag.name"> <MoreOptionsDropdown :label="'Actions for Tag ' + props.tag.name">
<button <button
@click="emit('delete')"
:aria-label="'Delete Tag ' + props.tag.name" :aria-label="'Delete Tag ' + props.tag.name"
data-testid="tag_delete" data-testid="tag_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -19,8 +19,8 @@ const showCreateTagModal = ref(false);
<template> <template>
<TagCreateModal <TagCreateModal
v-model:show="showCreateTagModal" :createTag
:create-tag></TagCreateModal> v-model:show="showCreateTagModal"></TagCreateModal>
<div class="flow-root"> <div class="flow-root">
<div class="inline-block min-w-full align-middle"> <div class="inline-block min-w-full align-middle">
<div <div
@@ -29,18 +29,18 @@ const showCreateTagModal = ref(false);
style="grid-template-columns: 1fr 80px"> style="grid-template-columns: 1fr 80px">
<TagTableHeading></TagTableHeading> <TagTableHeading></TagTableHeading>
<div <div
v-if="tags.length === 0" class="col-span-5 py-24 text-center"
class="col-span-5 py-24 text-center"> v-if="tags.length === 0">
<FolderPlusIcon <FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon> class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold">No tags found</h3> <h3 class="text-white font-semibold">No tags found</h3>
<p v-if="canCreateTags()" class="pb-5"> <p class="pb-5" v-if="canCreateTags()">
Create your first tag now! Create your first tag now!
</p> </p>
<SecondaryButton <SecondaryButton
v-if="canCreateTags()" v-if="canCreateTags()"
:icon="PlusIcon"
@click="showCreateTagModal = true" @click="showCreateTagModal = true"
:icon="PlusIcon"
>Create your First Tag</SecondaryButton >Create your First Tag</SecondaryButton
> >
</div> </div>

View File

@@ -53,19 +53,19 @@ useFocus(taskNameInput, { initialValue: true });
v-model="taskName" v-model="taskName"
type="text" type="text"
placeholder="Task Name" placeholder="Task Name"
@keydown.enter="submit()"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="taskName" autocomplete="taskName" />
@keydown.enter="submit()" />
</div> </div>
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<ProjectDropdown :model-value="projectId"></ProjectDropdown> <ProjectDropdown :modelValue="projectId"></ProjectDropdown>
</div> </div>
</div> </div>
<EstimatedTimeSection <EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()" v-if="isAllowedToPerformPremiumAction()"
v-model="estimatedTime" @submit="submit()"
@submit="submit()"></EstimatedTimeSection> v-model="estimatedTime"></EstimatedTimeSection>
</template> </template>
<template #footer> <template #footer>
<SecondaryButton @click="show = false"> Cancel </SecondaryButton> <SecondaryButton @click="show = false"> Cancel </SecondaryButton>

View File

@@ -50,16 +50,16 @@ useFocus(taskNameInput, { initialValue: true });
v-model="taskBody.name" v-model="taskBody.name"
type="text" type="text"
placeholder="Task Name" placeholder="Task Name"
@keydown.enter="submit()"
class="mt-1 block w-full" class="mt-1 block w-full"
required required
autocomplete="taskName" autocomplete="taskName" />
@keydown.enter="submit()" />
</div> </div>
</div> </div>
<EstimatedTimeSection <EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()" v-if="isAllowedToPerformPremiumAction()"
v-model="taskBody.estimated_time" @submit="submit()"
@submit="submit()"></EstimatedTimeSection> v-model="taskBody.estimated_time"></EstimatedTimeSection>
</template> </template>
<template #footer> <template #footer>
<SecondaryButton @click="show = false"> Cancel </SecondaryButton> <SecondaryButton @click="show = false"> Cancel </SecondaryButton>

View File

@@ -21,30 +21,30 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Task ' + props.task.name"> <MoreOptionsDropdown :label="'Actions for Task ' + props.task.name">
<div class="min-w-[150px]"> <div class="min-w-[150px]">
<button <button
@click="emit('edit')"
v-if="canUpdateTasks()" v-if="canUpdateTasks()"
:aria-label="'Edit Task ' + props.task.name" :aria-label="'Edit Task ' + props.task.name"
data-testid="task_edit" data-testid="task_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('edit')">
<PencilSquareIcon <PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon> class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span> <span>Edit</span>
</button> </button>
<button <button
@click="emit('done')"
v-if="canUpdateTasks()" v-if="canUpdateTasks()"
:aria-label="'Mark Task ' + props.task.name + ' as done'" :aria-label="'Mark Task ' + props.task.name + ' as done'"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('done')">
<CheckCircleIcon class="w-5 text-icon-active"></CheckCircleIcon> <CheckCircleIcon class="w-5 text-icon-active"></CheckCircleIcon>
<span v-if="props.task.is_done">Mark as active</span> <span v-if="props.task.is_done">Mark as active</span>
<span v-else>Mark as done</span> <span v-else>Mark as done</span>
</button> </button>
<button <button
v-if="canDeleteTasks()" @click="emit('delete')"
:aria-label="'Delete Task ' + props.task.name" :aria-label="'Delete Task ' + props.task.name"
v-if="canDeleteTasks()"
data-testid="task_delete" data-testid="task_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span> <span>Delete</span>
</button> </button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Task) {
<template> <template>
<MultiselectDropdown <MultiselectDropdown
search-placeholder="Search for a Task..." searchPlaceholder="Search for a Task..."
:items="tasks" :items="tasks"
:get-key-from-item="getKeyFromItem" :get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem"> :get-name-for-item="getNameForItem">

View File

@@ -19,8 +19,8 @@ const createTask = ref(false);
<template> <template>
<TaskCreateModal <TaskCreateModal
v-model:show="createTask" :project-id="props.projectId"
:project-id="props.projectId"></TaskCreateModal> v-model:show="createTask"></TaskCreateModal>
<div class="flow-root"> <div class="flow-root">
<div class="inline-block min-w-full align-middle"> <div class="inline-block min-w-full align-middle">
<div <div
@@ -37,18 +37,18 @@ const createTask = ref(false);
"> ">
<TaskTableHeading></TaskTableHeading> <TaskTableHeading></TaskTableHeading>
<div <div
v-if="tasks.length === 0" class="col-span-5 py-24 text-center"
class="col-span-5 py-24 text-center"> v-if="tasks.length === 0">
<PlusCircleIcon <PlusCircleIcon
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon> class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<h3 class="text-white font-semibold">No tasks found</h3> <h3 class="text-white font-semibold">No tasks found</h3>
<p v-if="canCreateTasks()" class="pb-5"> <p class="pb-5" v-if="canCreateTasks()">
Create your first task now! Create your first task now!
</p> </p>
<SecondaryButton <SecondaryButton
v-if="canCreateTasks()" v-if="canCreateTasks()"
:icon="PlusIcon"
@click="createTask = true" @click="createTask = true"
:icon="PlusIcon"
>Create your First Task >Create your First Task
</SecondaryButton> </SecondaryButton>
</div> </div>

View File

@@ -75,8 +75,8 @@ const showTaskEditModal = ref(false);
@delete="deleteTask"></TaskMoreOptionsDropdown> @delete="deleteTask"></TaskMoreOptionsDropdown>
</div> </div>
<TaskEditModal <TaskEditModal
v-model:show="showTaskEditModal" :task="task"
:task="task"></TaskEditModal> v-model:show="showTaskEditModal"></TaskEditModal>
</TableRow> </TableRow>
</template> </template>

View File

@@ -11,8 +11,8 @@ const showUpgradeModal = ref(false);
solidtime Professional. solidtime Professional.
</UpgradeModal> </UpgradeModal>
<button <button
class="inline-flex bg-secondary hover:bg-tertiary px-2 py-1 rounded border border-border-secondary hover:border-border-tertiary items-center space-x-1" @click.prevent="showUpgradeModal = true"
@click.prevent="showUpgradeModal = true"> class="inline-flex bg-secondary hover:bg-tertiary px-2 py-1 rounded border border-border-secondary hover:border-border-tertiary items-center space-x-1">
<LockClosedIcon class="w-3 text-text-tertiary"></LockClosedIcon> <LockClosedIcon class="w-3 text-text-tertiary"></LockClosedIcon>
<span class="text-xs text-text-secondary font-semibold"> Upgrade </span> <span class="text-xs text-text-secondary font-semibold"> Upgrade </span>
</button> </button>

View File

@@ -50,12 +50,12 @@ const show = defineModel('show', { default: false });
v-if="isBillingActivated() && canManageBilling()" v-if="isBillingActivated() && canManageBilling()"
href="/billing"> href="/billing">
<PrimaryButton <PrimaryButton
v-if="
isBillingActivated() && canUpdateOrganization()
"
type="button" type="button"
class="mt-6" class="mt-6"
:icon="CreditCardIcon"> :icon="CreditCardIcon"
v-if="
isBillingActivated() && canUpdateOrganization()
">
Go to Billing Go to Billing
</PrimaryButton> </PrimaryButton>
</Link> </Link>

View File

@@ -32,8 +32,8 @@ const isRunningInDifferentOrganization = computed(() => {
<template> <template>
<div class="pt-3 pb-2.5 px-2 flex justify-between items-center relative"> <div class="pt-3 pb-2.5 px-2 flex justify-between items-center relative">
<div <div
v-if="isRunningInDifferentOrganization" class="absolute w-full h-full backdrop-blur-sm z-10 flex items-center justify-center"
class="absolute w-full h-full backdrop-blur-sm z-10 flex items-center justify-center"> v-if="isRunningInDifferentOrganization">
<div <div
class="w-full h-[calc(100%+10px)] absolute bg-default-background opacity-75 backdrop-blur-sm"></div> class="w-full h-[calc(100%+10px)] absolute bg-default-background opacity-75 backdrop-blur-sm"></div>
<div class="flex space-x-3 items-center w-full z-20 justify-center"> <div class="flex space-x-3 items-center w-full z-20 justify-center">
@@ -50,7 +50,7 @@ const isRunningInDifferentOrganization = computed(() => {
</div> </div>
<TimeTrackerStartStop <TimeTrackerStartStop
:active="isActive" :active="isActive"
size="base" @changed="setActiveState"
@changed="setActiveState"></TimeTrackerStartStop> size="base"></TimeTrackerStartStop>
</div> </div>
</template> </template>

View File

@@ -15,8 +15,8 @@ defineProps<{
<DashboardCard title="Last 7 Days" :icon="CalendarIcon"> <DashboardCard title="Last 7 Days" :icon="CalendarIcon">
<DayOverviewCardEntry <DayOverviewCardEntry
v-for="day in last7Days" v-for="day in last7Days"
:key="day.date"
:class="last7Days.length === 7 ? 'last:border-0 first:pt-3' : ''" :class="last7Days.length === 7 ? 'last:border-0 first:pt-3' : ''"
:key="day.date"
:date="day.date" :date="day.date"
:history="day.history" :history="day.history"
:duration="day.duration"></DayOverviewCardEntry> :duration="day.duration"></DayOverviewCardEntry>

View File

@@ -20,8 +20,8 @@ const props = defineProps<{
<DashboardCard title="Recently Tracked Tasks" :icon="CheckCircleIcon"> <DashboardCard title="Recently Tracked Tasks" :icon="CheckCircleIcon">
<RecentlyTrackedTasksCardEntry <RecentlyTrackedTasksCardEntry
v-for="lastTask in props.latestTasks" v-for="lastTask in props.latestTasks"
:key="lastTask.id"
:class="props.latestTasks.length === 4 ? 'last:border-0' : ''" :class="props.latestTasks.length === 4 ? 'last:border-0' : ''"
:key="lastTask.id"
:project_id="lastTask.project_id" :project_id="lastTask.project_id"
:task_id="lastTask.id" :task_id="lastTask.id"
:title="lastTask.name"></RecentlyTrackedTasksCardEntry> :title="lastTask.name"></RecentlyTrackedTasksCardEntry>

View File

@@ -29,14 +29,14 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
:icon :icon
:current :current
:href></NavigationSidebarLink> :href></NavigationSidebarLink>
<CollapsibleRoot v-else v-model:open="open" <CollapsibleRoot v-model:open="open" v-else
><CollapsibleTrigger class="w-full group py-0.5"> ><CollapsibleTrigger class="w-full group py-0.5">
<div <div
class="text-muted group-hover:text-white group-hover:bg-menu-active group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center justify-between"> class="text-muted group-hover:text-white group-hover:bg-menu-active group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center justify-between">
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-2">
<component <component
:is="icon"
v-if="icon" v-if="icon"
:is="icon"
:class="[ :class="[
current current
? 'text-icon-active' ? 'text-icon-active'
@@ -59,8 +59,8 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
<CollapsibleContent class="CollapsibleContent"> <CollapsibleContent class="CollapsibleContent">
<div class="px-3.5"> <div class="px-3.5">
<ul <ul
v-if="subItems" class="flex min-w-0 flex-col border-l border-border-secondary px-3 w-full my-0.5"
class="flex min-w-0 flex-col border-l border-border-secondary px-3 w-full my-0.5"> v-if="subItems">
<li <li
v-for="subItem in subItems" v-for="subItem in subItems"
:key="subItem.title" :key="subItem.title"

View File

@@ -19,8 +19,8 @@ defineProps<{
'group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center', 'group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center',
]"> ]">
<component <component
:is="icon"
v-if="icon" v-if="icon"
:is="icon"
:class="[ :class="[
current current
? 'text-icon-active' ? 'text-icon-active'

View File

@@ -5,8 +5,8 @@
<div class="flex w-full flex-col items-center space-y-4 sm:items-end"> <div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<Notification <Notification
v-for="notification in notifications" v-for="notification in notifications"
:key="notification.uuid"
:type="notification.type" :type="notification.type"
:key="notification.uuid"
:title="notification.title" :title="notification.title"
:message="notification.message"></Notification> :message="notification.message"></Notification>
</div> </div>

View File

@@ -110,29 +110,29 @@ const { tags } = storeToRefs(useTagsStore());
<CardTitle title="Time Tracker" :icon="ClockIcon"></CardTitle> <CardTitle title="Time Tracker" :icon="ClockIcon"></CardTitle>
<div class="relative"> <div class="relative">
<TimeTrackerRunningInDifferentOrganizationOverlay <TimeTrackerRunningInDifferentOrganizationOverlay
@switchOrganization="switchToTimeEntryOrganization"
v-if=" v-if="
isRunningInDifferentOrganization isRunningInDifferentOrganization
" "></TimeTrackerRunningInDifferentOrganizationOverlay>
@switch-organization="switchToTimeEntryOrganization"></TimeTrackerRunningInDifferentOrganizationOverlay>
<TimeTrackerControls <TimeTrackerControls
v-model:current-time-entry="currentTimeEntry" :createProject
v-model:live-timer="now" :enableEstimatedTime="isAllowedToPerformPremiumAction()"
:create-project :canCreateProject="canCreateProjects()"
:enable-estimated-time="isAllowedToPerformPremiumAction()" :createClient
:can-create-project="canCreateProjects()"
:create-client
:clients :clients
:tags :tags
:tasks :tasks
:projects :projects
:create-tag :createTag
:is-active :isActive
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
@start-live-timer="startLiveTimer" v-model:currentTimeEntry="currentTimeEntry"
@stop-live-timer="stopLiveTimer" v-model:liveTimer="now"
@start-timer="setActiveState(true)" @startLiveTimer="startLiveTimer"
@stop-timer="setActiveState(false)" @stopLiveTimer="stopLiveTimer"
@update-time-entry="updateTimeEntry"></TimeTrackerControls> @startTimer="setActiveState(true)"
@stopTimer="setActiveState(false)"
@updateTimeEntry="updateTimeEntry"></TimeTrackerControls>
</div> </div>
</template> </template>

View File

@@ -12,7 +12,7 @@ function openDesktopGithubRepo() {
</script> </script>
<template> <template>
<div v-if="showReleaseInfo" class="py-4 hidden lg:block"> <div class="py-4 hidden lg:block" v-if="showReleaseInfo">
<div <div
class="rounded-lg px-2.5 py-2 bg-card-background border border-border-secondary"> class="rounded-lg px-2.5 py-2 bg-card-background border border-border-secondary">
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
@@ -23,8 +23,8 @@ function openDesktopGithubRepo() {
</div> </div>
<button> <button>
<XMarkIcon <XMarkIcon
class="w-3.5 text-text-tertiary hover:text-text-secondary" @click="showReleaseInfo = false"
@click="showReleaseInfo = false"></XMarkIcon> class="w-3.5 text-text-tertiary hover:text-text-secondary"></XMarkIcon>
</button> </button>
</div> </div>
@@ -34,9 +34,9 @@ function openDesktopGithubRepo() {
now. now.
</p> </p>
<SecondaryButton <SecondaryButton
@click="openDesktopGithubRepo"
size="small" size="small"
class="w-full text-center justify-center mt-1.5" class="w-full text-center justify-center mt-1.5"
@click="openDesktopGithubRepo"
>Download now</SecondaryButton >Download now</SecondaryButton
> >
</div> </div>

View File

@@ -85,8 +85,8 @@ const page = usePage<{
class="border-b border-default-background-separator pb-2 flex justify-between"> class="border-b border-default-background-separator pb-2 flex justify-between">
<OrganizationSwitcher class="w-full"></OrganizationSwitcher> <OrganizationSwitcher class="w-full"></OrganizationSwitcher>
<XMarkIcon <XMarkIcon
class="w-8 lg:hidden" @click="showSidebarMenu = false"
@click="showSidebarMenu = false"></XMarkIcon> class="w-8 lg:hidden"></XMarkIcon>
</div> </div>
<div class="border-b border-default-background-separator"> <div class="border-b border-default-background-separator">
<CurrentSidebarTimer></CurrentSidebarTimer> <CurrentSidebarTimer></CurrentSidebarTimer>
@@ -162,8 +162,8 @@ const page = usePage<{
route('clients') route('clients')
"></NavigationSidebarItem> "></NavigationSidebarItem>
<NavigationSidebarItem <NavigationSidebarItem
v-if="canViewMembers()"
title="Members" title="Members"
v-if="canViewMembers()"
:icon="UserGroupIcon" :icon="UserGroupIcon"
:current="route().current('members')" :current="route().current('members')"
:href=" :href="
@@ -238,8 +238,8 @@ const page = usePage<{
<div <div
class="lg:hidden w-full px-3 py-1 border-b border-b-default-background-separator text-muted flex justify-between items-center"> class="lg:hidden w-full px-3 py-1 border-b border-b-default-background-separator text-muted flex justify-between items-center">
<Bars3Icon <Bars3Icon
class="w-7 text-muted" @click="showSidebarMenu = !showSidebarMenu"
@click="showSidebarMenu = !showSidebarMenu"></Bars3Icon> class="w-7 text-muted"></Bars3Icon>
<OrganizationSwitcher></OrganizationSwitcher> <OrganizationSwitcher></OrganizationSwitcher>
</div> </div>

View File

@@ -151,7 +151,7 @@ const page = usePage<{
</InputLabel> </InputLabel>
</div> </div>
<div v-if="page.props.newsletter_consent" class="mt-4"> <div class="mt-4" v-if="page.props.newsletter_consent">
<InputLabel for="newsletter_consent"> <InputLabel for="newsletter_consent">
<div class="flex items-center"> <div class="flex items-center">
<Checkbox <Checkbox

View File

@@ -74,7 +74,7 @@ function refreshDashboardData() {
<MainContainer <MainContainer
class="grid gap-5 sm:gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 pt-3 sm:pt-5 pb-4 sm:pb-6 border-b border-default-background-separator items-stretch"> class="grid gap-5 sm:gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 pt-3 sm:pt-5 pb-4 sm:pb-6 border-b border-default-background-separator items-stretch">
<RecentlyTrackedTasksCard <RecentlyTrackedTasksCard
:latest-tasks="props.latestTasks"></RecentlyTrackedTasksCard> :latestTasks="props.latestTasks"></RecentlyTrackedTasksCard>
<LastSevenDaysCard <LastSevenDaysCard
:last7-days="props.lastSevenDays"></LastSevenDaysCard> :last7-days="props.lastSevenDays"></LastSevenDaysCard>
<ActivityGraphCard <ActivityGraphCard
@@ -84,13 +84,13 @@ function refreshDashboardData() {
<TeamActivityCard <TeamActivityCard
v-if="canViewMembers()" v-if="canViewMembers()"
class="flex lg:hidden xl:flex" class="flex lg:hidden xl:flex"
:latest-team-activity=" :latestTeamActivity="
props.latestTeamActivity props.latestTeamActivity
"></TeamActivityCard> "></TeamActivityCard>
</MainContainer> </MainContainer>
<MainContainer class="py-5"> <MainContainer class="py-5">
<ThisWeekOverview <ThisWeekOverview
:weekly-project-overview="props.weeklyProjectOverview" :weeklyProjectOverview="props.weeklyProjectOverview"
:total-weekly-billable-amount="props.totalWeeklyBillableAmount" :total-weekly-billable-amount="props.totalWeeklyBillableAmount"
:total-weekly-billable-time="props.totalWeeklyBillableTime" :total-weekly-billable-time="props.totalWeeklyBillableTime"
:total-weekly-time="props.totalWeeklyTime" :total-weekly-time="props.totalWeeklyTime"

View File

@@ -52,8 +52,8 @@ function isActiveTab(tab: string) {
>Invite member</SecondaryButton >Invite member</SecondaryButton
> >
<MemberInviteModal <MemberInviteModal
v-model:show="inviteMember"
:available-roles="availableRoles" :available-roles="availableRoles"
v-model:show="inviteMember"
@close="activeTab = 'invitations'"></MemberInviteModal> @close="activeTab = 'invitations'"></MemberInviteModal>
</MainContainer> </MainContainer>
<MemberTable v-if="activeTab === 'all'"></MemberTable> <MemberTable v-if="activeTab === 'all'"></MemberTable>

View File

@@ -204,9 +204,9 @@ const page = usePage<{
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<InputLabel for="timezone" value="Timezone" /> <InputLabel for="timezone" value="Timezone" />
<select <select
name="timezone"
id="timezone" id="timezone"
v-model="form.timezone" v-model="form.timezone"
name="timezone"
required required
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm"> class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a Timezone</option> <option value="" disabled>Select a Timezone</option>
@@ -225,9 +225,9 @@ const page = usePage<{
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<InputLabel for="week_start" value="Start of the week" /> <InputLabel for="week_start" value="Start of the week" />
<select <select
name="week_start"
id="week_start" id="week_start"
v-model="form.week_start" v-model="form.week_start"
name="week_start"
required required
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm"> class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a week day</option> <option value="" disabled>Select a week day</option>

View File

@@ -129,15 +129,15 @@ const shownTasks = computed(() => {
</nav> </nav>
<div> <div>
<SecondaryButton <SecondaryButton
v-if="canCreateProjects()"
:icon="PencilSquareIcon" :icon="PencilSquareIcon"
@click="showEditProjectModal = true"> @click="showEditProjectModal = true"
v-if="canCreateProjects()">
Edit Project Edit Project
</SecondaryButton> </SecondaryButton>
<ProjectEditModal <ProjectEditModal
v-if="project" v-if="project"
v-model:show="showEditProjectModal" :originalProject="project"
:original-project="project"></ProjectEditModal> v-model:show="showEditProjectModal"></ProjectEditModal>
</div> </div>
</MainContainer> </MainContainer>
<MainContainer> <MainContainer>
@@ -168,8 +168,8 @@ const shownTasks = computed(() => {
>Create Task >Create Task
</SecondaryButton> </SecondaryButton>
<TaskCreateModal <TaskCreateModal
v-model:show="createTask" :project-id="projectId"
:project-id="projectId"></TaskCreateModal> v-model:show="createTask"></TaskCreateModal>
</div> </div>
</template> </template>
</CardTitle> </CardTitle>
@@ -188,11 +188,11 @@ const shownTasks = computed(() => {
Add Member Add Member
</SecondaryButton> </SecondaryButton>
<ProjectMemberCreateModal <ProjectMemberCreateModal
:project-id="projectId"
:existing-members="projectMembers"
v-model:show=" v-model:show="
createProjectMember createProjectMember
" "></ProjectMemberCreateModal>
:project-id="projectId"
:existing-members="projectMembers"></ProjectMemberCreateModal>
</template> </template>
</CardTitle> </CardTitle>
<Card> <Card>

View File

@@ -94,13 +94,13 @@ const showBillableRate = computed(() => {
>Create Project >Create Project
</SecondaryButton> </SecondaryButton>
<ProjectCreateModal <ProjectCreateModal
v-model:show="showCreateProjectModal" :createProject
:create-project :enableEstimatedTime="isAllowedToPerformPremiumAction"
:enable-estimated-time="isAllowedToPerformPremiumAction" :createClient
:create-client
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
:clients="clients" :clients="clients"
@submit="createProject"></ProjectCreateModal> @submit="createProject"
v-model:show="showCreateProjectModal"></ProjectCreateModal>
</MainContainer> </MainContainer>
<ProjectTable <ProjectTable
:show-billable-rate="showBillableRate" :show-billable-rate="showBillableRate"

View File

@@ -281,7 +281,7 @@ const tableData = computed(() => {
class="overflow-hidden"> class="overflow-hidden">
<ReportingExportModal <ReportingExportModal
v-model:show="showExportModal" v-model:show="showExportModal"
:export-url="exportUrl"></ReportingExportModal> :exportUrl="exportUrl"></ReportingExportModal>
<MainContainer <MainContainer
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center"> class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6"> <div class="flex items-center space-x-3 sm:space-x-6">
@@ -292,7 +292,7 @@ const tableData = computed(() => {
<ReportingExportButton <ReportingExportButton
:download="downloadExport"></ReportingExportButton> :download="downloadExport"></ReportingExportButton>
<ReportSaveButton <ReportSaveButton
:report-properties="reportProperties"></ReportSaveButton> :reportProperties="reportProperties"></ReportSaveButton>
</div> </div>
</MainContainer> </MainContainer>
<div class="py-2.5 w-full border-b border-default-background-separator"> <div class="py-2.5 w-full border-b border-default-background-separator">
@@ -302,9 +302,9 @@ const tableData = computed(() => {
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4"> class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
<div class="text-sm font-medium">Filters</div> <div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown <MemberMultiselectDropdown
v-model="selectedMembers" @submit="updateReporting"
@submit="updateReporting"> v-model="selectedMembers">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedMembers.length" :count="selectedMembers.length"
:active="selectedMembers.length > 0" :active="selectedMembers.length > 0"
@@ -313,9 +313,9 @@ const tableData = computed(() => {
</template> </template>
</MemberMultiselectDropdown> </MemberMultiselectDropdown>
<ProjectMultiselectDropdown <ProjectMultiselectDropdown
v-model="selectedProjects" @submit="updateReporting"
@submit="updateReporting"> v-model="selectedProjects">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedProjects.length" :count="selectedProjects.length"
:active="selectedProjects.length > 0" :active="selectedProjects.length > 0"
@@ -324,9 +324,9 @@ const tableData = computed(() => {
</template> </template>
</ProjectMultiselectDropdown> </ProjectMultiselectDropdown>
<TaskMultiselectDropdown <TaskMultiselectDropdown
v-model="selectedTasks" @submit="updateReporting"
@submit="updateReporting"> v-model="selectedTasks">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedTasks.length" :count="selectedTasks.length"
:active="selectedTasks.length > 0" :active="selectedTasks.length > 0"
@@ -335,20 +335,20 @@ const tableData = computed(() => {
</template> </template>
</TaskMultiselectDropdown> </TaskMultiselectDropdown>
<ClientMultiselectDropdown <ClientMultiselectDropdown
v-model="selectedClients" @submit="updateReporting"
@submit="updateReporting"> v-model="selectedClients">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
title="Clients" title="Clients"
:icon="FolderIcon"></ReportingFilterBadge> :icon="FolderIcon"></ReportingFilterBadge>
</template> </template>
</ClientMultiselectDropdown> </ClientMultiselectDropdown>
<TagDropdown <TagDropdown
@submit="updateReporting"
:createTag
v-model="selectedTags" v-model="selectedTags"
:create-tag :tags="tags">
:tags="tags" <template v-slot:trigger>
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedTags.length" :count="selectedTags.length"
:active="selectedTags.length > 0" :active="selectedTags.length > 0"
@@ -358,6 +358,7 @@ const tableData = computed(() => {
</TagDropdown> </TagDropdown>
<SelectDropdown <SelectDropdown
@changed="updateReporting"
v-model="billable" v-model="billable"
:get-key-from-item="(item) => item.value" :get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label" :get-name-for-item="(item) => item.label"
@@ -374,9 +375,8 @@ const tableData = computed(() => {
label: 'Non Billable', label: 'Non Billable',
value: 'false', value: 'false',
}, },
]" ]">
@changed="updateReporting"> <template v-slot:trigger>
<template #trigger>
<ReportingFilterBadge <ReportingFilterBadge
:active="billable !== null" :active="billable !== null"
:title=" :title="
@@ -399,8 +399,8 @@ const tableData = computed(() => {
<MainContainer> <MainContainer>
<div class="pt-10 w-full px-3 relative"> <div class="pt-10 w-full px-3 relative">
<ReportingChart <ReportingChart
:grouped-type="aggregatedGraphTimeEntries?.grouped_type" :groupedType="aggregatedGraphTimeEntries?.grouped_type"
:grouped-data=" :groupedData="
aggregatedGraphTimeEntries?.grouped_data aggregatedGraphTimeEntries?.grouped_data
"></ReportingChart> "></ReportingChart>
</div> </div>
@@ -413,18 +413,18 @@ const tableData = computed(() => {
class="text-sm flex text-white items-center space-x-3 font-medium px-6 border-b border-card-background-separator pb-3"> class="text-sm flex text-white items-center space-x-3 font-medium px-6 border-b border-card-background-separator pb-3">
<span>Group by</span> <span>Group by</span>
<ReportingGroupBySelect <ReportingGroupBySelect
v-model="group"
:group-by-options="groupByOptions" :group-by-options="groupByOptions"
@changed="updateTableReporting"></ReportingGroupBySelect> @changed="updateTableReporting"
v-model="group"></ReportingGroupBySelect>
<span>and</span> <span>and</span>
<ReportingGroupBySelect <ReportingGroupBySelect
v-model="subGroup"
:group-by-options=" :group-by-options="
groupByOptions.filter( groupByOptions.filter(
(el) => el.value !== group (el) => el.value !== group
) )
" "
@changed="updateTableReporting"></ReportingGroupBySelect> @changed="updateTableReporting"
v-model="subGroup"></ReportingGroupBySelect>
</div> </div>
<div <div
class="grid items-center" class="grid items-center"
@@ -473,8 +473,8 @@ const tableData = computed(() => {
</div> </div>
</template> </template>
<div <div
v-else class="chart flex flex-col items-center justify-center py-12 col-span-3"
class="chart flex flex-col items-center justify-center py-12 col-span-3"> v-else>
<p class="text-lg text-white font-semibold"> <p class="text-lg text-white font-semibold">
No time entries found No time entries found
</p> </p>

View File

@@ -252,7 +252,7 @@ async function downloadExport(format: ExportFormat) {
class="overflow-hidden"> class="overflow-hidden">
<ReportingExportModal <ReportingExportModal
v-model:show="showExportModal" v-model:show="showExportModal"
:export-url="exportUrl"></ReportingExportModal> :exportUrl="exportUrl"></ReportingExportModal>
<MainContainer <MainContainer
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center"> class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6"> <div class="flex items-center space-x-3 sm:space-x-6">
@@ -270,9 +270,9 @@ async function downloadExport(format: ExportFormat) {
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4"> class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
<div class="text-sm font-medium">Filters</div> <div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown <MemberMultiselectDropdown
v-model="selectedMembers" @submit="updateFilteredTimeEntries"
@submit="updateFilteredTimeEntries"> v-model="selectedMembers">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedMembers.length" :count="selectedMembers.length"
:active="selectedMembers.length > 0" :active="selectedMembers.length > 0"
@@ -281,9 +281,9 @@ async function downloadExport(format: ExportFormat) {
</template> </template>
</MemberMultiselectDropdown> </MemberMultiselectDropdown>
<ProjectMultiselectDropdown <ProjectMultiselectDropdown
v-model="selectedProjects" @submit="updateFilteredTimeEntries"
@submit="updateFilteredTimeEntries"> v-model="selectedProjects">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedProjects.length" :count="selectedProjects.length"
:active="selectedProjects.length > 0" :active="selectedProjects.length > 0"
@@ -292,9 +292,9 @@ async function downloadExport(format: ExportFormat) {
</template> </template>
</ProjectMultiselectDropdown> </ProjectMultiselectDropdown>
<TaskMultiselectDropdown <TaskMultiselectDropdown
v-model="selectedTasks" @submit="updateFilteredTimeEntries"
@submit="updateFilteredTimeEntries"> v-model="selectedTasks">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedTasks.length" :count="selectedTasks.length"
:active="selectedTasks.length > 0" :active="selectedTasks.length > 0"
@@ -303,20 +303,20 @@ async function downloadExport(format: ExportFormat) {
</template> </template>
</TaskMultiselectDropdown> </TaskMultiselectDropdown>
<ClientMultiselectDropdown <ClientMultiselectDropdown
v-model="selectedClients" @submit="updateFilteredTimeEntries"
@submit="updateFilteredTimeEntries"> v-model="selectedClients">
<template #trigger> <template v-slot:trigger>
<ReportingFilterBadge <ReportingFilterBadge
title="Clients" title="Clients"
:icon="FolderIcon"></ReportingFilterBadge> :icon="FolderIcon"></ReportingFilterBadge>
</template> </template>
</ClientMultiselectDropdown> </ClientMultiselectDropdown>
<TagDropdown <TagDropdown
@submit="updateFilteredTimeEntries"
:createTag
v-model="selectedTags" v-model="selectedTags"
:create-tag :tags="tags">
:tags="tags" <template v-slot:trigger>
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge <ReportingFilterBadge
:count="selectedTags.length" :count="selectedTags.length"
:active="selectedTags.length > 0" :active="selectedTags.length > 0"
@@ -326,6 +326,7 @@ async function downloadExport(format: ExportFormat) {
</TagDropdown> </TagDropdown>
<SelectDropdown <SelectDropdown
@changed="updateFilteredTimeEntries"
v-model="billable" v-model="billable"
:get-key-from-item="(item) => item.value" :get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label" :get-name-for-item="(item) => item.label"
@@ -342,9 +343,8 @@ async function downloadExport(format: ExportFormat) {
label: 'Non Billable', label: 'Non Billable',
value: 'false', value: 'false',
}, },
]" ]">
@changed="updateFilteredTimeEntries"> <template v-slot:trigger>
<template #trigger>
<ReportingFilterBadge <ReportingFilterBadge
:active="billable !== null" :active="billable !== null"
:title=" :title="
@@ -366,9 +366,12 @@ async function downloadExport(format: ExportFormat) {
</div> </div>
<TimeEntryMassActionRow <TimeEntryMassActionRow
:selected-time-entries="selectedTimeEntries" :selected-time-entries="selectedTimeEntries"
:can-create-project="canCreateProjects()" :canCreateProject="canCreateProjects()"
:enable-estimated-time="isAllowedToPerformPremiumAction()" :enableEstimatedTime="isAllowedToPerformPremiumAction()"
@submit="clearSelectionAndState"
:delete-selected="deleteSelected" :delete-selected="deleteSelected"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"
:all-selected="selectedTimeEntries.length === timeEntries.length" :all-selected="selectedTimeEntries.length === timeEntries.length"
:projects="projects" :projects="projects"
:tasks="tasks" :tasks="tasks"
@@ -384,37 +387,34 @@ async function downloadExport(format: ExportFormat) {
" "
:create-project="createProject" :create-project="createProject"
:create-client="createClient" :create-client="createClient"
:create-tag="createTag" :createTag="createTag"></TimeEntryMassActionRow>
@submit="clearSelectionAndState"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"></TimeEntryMassActionRow>
<div class="w-full relative"> <div class="w-full relative">
<div v-for="entry in timeEntries" :key="entry.id"> <div v-for="entry in timeEntries" :key="entry.id">
<TimeEntryRow <TimeEntryRow
:selected="selectedTimeEntries.includes(entry)" :selected="selectedTimeEntries.includes(entry)"
:can-create-project="canCreateProjects()"
:create-client
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:projects="projects"
:tasks="tasks"
:tags="tags"
:clients
:create-tag
:update-time-entry
:on-start-stop-click="() => startTimeEntryFromExisting(entry)"
:delete-time-entry="() => deleteTimeEntries([entry])"
:currency="getOrganizationCurrencyString()"
:members="members"
show-date
show-member
:time-entry="entry"
@selected="selectedTimeEntries.push(entry)" @selected="selectedTimeEntries.push(entry)"
:canCreateProject="canCreateProjects()"
@unselected=" @unselected="
selectedTimeEntries = selectedTimeEntries.filter( selectedTimeEntries = selectedTimeEntries.filter(
(item) => item.id !== entry.id (item) => item.id !== entry.id
) )
"></TimeEntryRow> "
:createClient
:createProject
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:projects="projects"
:tasks="tasks"
:tags="tags"
:clients
:createTag
:updateTimeEntry
:onStartStopClick="() => startTimeEntryFromExisting(entry)"
:deleteTimeEntry="() => deleteTimeEntries([entry])"
:currency="getOrganizationCurrencyString()"
:members="members"
showDate
showMember
:time-entry="entry"></TimeEntryRow>
</div> </div>
<div v-if="timeEntries.length === 0"> <div v-if="timeEntries.length === 0">
<div class="text-center pt-12"> <div class="text-center pt-12">
@@ -431,10 +431,10 @@ async function downloadExport(format: ExportFormat) {
</div> </div>
<PaginationRoot <PaginationRoot
v-model:page="currentPage"
:total="totalPages" :total="totalPages"
:items-per-page="pageLimit" :items-per-page="pageLimit"
class="flex justify-center items-center py-8" class="flex justify-center items-center py-8"
v-model:page="currentPage"
:sibling-count="1" :sibling-count="1"
show-edges> show-edges>
<PaginationList <PaginationList
@@ -485,13 +485,13 @@ async function downloadExport(format: ExportFormat) {
</template> </template>
<style lang="postcss"> <style lang="postcss">
.navigation-item { .navigation-item {
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring; @apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
} }
.pagination-item { .pagination-item {
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring; @apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
} }
.pagination-item[data-selected] { .pagination-item[data-selected] {
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-ring transition ease-in-out duration-150; @apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-white/80 transition ease-in-out duration-150;
} }
</style> </style>

View File

@@ -101,12 +101,12 @@ watch(currentPage, () => {
v-if="isBillingActivated() && canManageBilling()" v-if="isBillingActivated() && canManageBilling()"
href="/billing"> href="/billing">
<PrimaryButton <PrimaryButton
v-if="
isBillingActivated() && canUpdateOrganization()
"
type="button" type="button"
class="mt-6" class="mt-6"
:icon="CreditCardIcon"> :icon="CreditCardIcon"
v-if="
isBillingActivated() && canUpdateOrganization()
">
Go to Billing Go to Billing
</PrimaryButton> </PrimaryButton>
</Link> </Link>
@@ -120,10 +120,10 @@ watch(currentPage, () => {
<PaginationRoot <PaginationRoot
v-if="reports.length > 0 || isAllowedToPerformPremiumAction()" v-if="reports.length > 0 || isAllowedToPerformPremiumAction()"
v-model:page="currentPage"
:total="totalPages" :total="totalPages"
:items-per-page="pageLimit" :items-per-page="pageLimit"
class="flex justify-center items-center py-8" class="flex justify-center items-center py-8"
v-model:page="currentPage"
:sibling-count="1" :sibling-count="1"
show-edges> show-edges>
<PaginationList <PaginationList
@@ -174,13 +174,13 @@ watch(currentPage, () => {
</template> </template>
<style lang="postcss"> <style lang="postcss">
.navigation-item { .navigation-item {
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring; @apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
} }
.pagination-item { .pagination-item {
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring; @apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
} }
.pagination-item[data-selected] { .pagination-item[data-selected] {
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-ring transition ease-in-out duration-150; @apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-white/80 transition ease-in-out duration-150;
} }
</style> </style>

View File

@@ -151,8 +151,8 @@ function getGroupLabel(key: string) {
<MainContainer> <MainContainer>
<div class="pt-10 w-full px-3 relative"> <div class="pt-10 w-full px-3 relative">
<ReportingChart <ReportingChart
:grouped-type="aggregatedGraphTimeEntries?.grouped_type" :groupedType="aggregatedGraphTimeEntries?.grouped_type"
:grouped-data=" :groupedData="
aggregatedGraphTimeEntries?.grouped_data aggregatedGraphTimeEntries?.grouped_data
"></ReportingChart> "></ReportingChart>
</div> </div>
@@ -217,8 +217,8 @@ function getGroupLabel(key: string) {
</div> </div>
</template> </template>
<div <div
v-else class="chart flex flex-col items-center justify-center py-12 col-span-3"
class="chart flex flex-col items-center justify-center py-12 col-span-3"> v-else>
<p class="text-lg text-white font-semibold"> <p class="text-lg text-white font-semibold">
No time entries found No time entries found
</p> </p>

View File

@@ -31,9 +31,9 @@ async function createTag(tag: string) {
>Create Tag >Create Tag
</SecondaryButton> </SecondaryButton>
<TagCreateModal <TagCreateModal
v-model:show="showCreateTagModal" :createTag="createTag"
:create-tag="createTag"></TagCreateModal> v-model:show="showCreateTagModal"></TagCreateModal>
</MainContainer> </MainContainer>
<TagTable :create-tag="createTag"></TagTable> <TagTable :createTag="createTag"></TagTable>
</AppLayout> </AppLayout>
</template> </template>

View File

@@ -191,9 +191,9 @@ const showResultModal = ref(false);
<div> <div>
<InputLabel for="importType" value="Import Type" /> <InputLabel for="importType" value="Import Type" />
<select <select
name="importType"
id="importType" id="importType"
v-model="importType" v-model="importType"
name="importType"
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm"> class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option :value="null" selected disabled> <option :value="null" selected disabled>
Select an import type to get instructions... Select an import type to get instructions...
@@ -206,8 +206,8 @@ const showResultModal = ref(false);
</option> </option>
</select> </select>
<div <div
v-if="currentImporterDescription" class="py-3 text-white"
class="py-3 text-white"> v-if="currentImporterDescription">
<div class="font-semibold text-muted py-1"> <div class="font-semibold text-muted py-1">
Instructions: Instructions:
</div> </div>
@@ -233,12 +233,12 @@ const showResultModal = ref(false);
>Upload a Toggl/Clockify Export</span >Upload a Toggl/Clockify Export</span
> >
<input <input
id="file-upload"
ref="importFile" ref="importFile"
id="file-upload"
name="file-upload" name="file-upload"
v-on:change="updateFiles"
type="file" type="file"
class="sr-only" class="sr-only" />
@change="updateFiles" />
</label> </label>
</div> </div>
<p class="text-xs leading-5 text-muted"> <p class="text-xs leading-5 text-muted">

View File

@@ -62,10 +62,10 @@ function checkForConfirmationModal() {
<template #form> <template #form>
<OrganizationBillableRateModal <OrganizationBillableRateModal
v-model:show="showConfirmationModal" v-model:show="showConfirmationModal"
@submit="submit"
:new-billable-rate=" :new-billable-rate="
organizationBody.billable_rate organizationBody.billable_rate
" "></OrganizationBillableRateModal>
@submit="submit"></OrganizationBillableRateModal>
<!-- Organization Owner Information --> <!-- Organization Owner Information -->
<div class="col-span-6"> <div class="col-span-6">
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
@@ -75,8 +75,8 @@ function checkForConfirmationModal() {
value="Organization Billable Rate" /> value="Organization Billable Rate" />
<BillableRateInput <BillableRateInput
v-if="organization" v-if="organization"
v-model="organizationBody.billable_rate"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
v-model="organizationBody.billable_rate"
name="organizationBillableRate"></BillableRateInput> name="organizationBillableRate"></BillableRateInput>
</div> </div>
</div> </div>
@@ -86,10 +86,10 @@ function checkForConfirmationModal() {
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<Checkbox <Checkbox
v-if="organization" v-if="organization"
id="organizationShowBillableRatesToEmployees"
v-model:checked=" v-model:checked="
organizationBody.employees_can_see_billable_rates organizationBody.employees_can_see_billable_rates
"></Checkbox> "
id="organizationShowBillableRatesToEmployees"></Checkbox>
<InputLabel <InputLabel
for="organizationShowBillableRatesToEmployees" for="organizationShowBillableRatesToEmployees"
value="Show Billable Rates to Employees" /> value="Show Billable Rates to Employees" />

View File

@@ -89,9 +89,9 @@ const updateTeamName = () => {
<div class="col-span-6 sm:col-span-4"> <div class="col-span-6 sm:col-span-4">
<InputLabel for="currency" value="Currency" /> <InputLabel for="currency" value="Currency" />
<select <select
name="currency"
id="currency" id="currency"
v-model="form.currency" v-model="form.currency"
name="currency"
:disabled="!permissions.canUpdateTeam" :disabled="!permissions.canUpdateTeam"
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm"> class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a currency</option> <option value="" disabled>Select a currency</option>

View File

@@ -120,16 +120,16 @@ function deleteSelected() {
<template> <template>
<TimeEntryCreateModal <TimeEntryCreateModal
v-model:show="showManualTimeEntryModal" :enableEstimatedTime="isAllowedToPerformPremiumAction()"
:enable-estimated-time="isAllowedToPerformPremiumAction()" :createProject="createProject"
:create-project="createProject" :createClient="createClient"
:create-client="createClient" :createTag="createTag"
:create-tag="createTag" :createTimeEntry="createTimeEntry"
:create-time-entry="createTimeEntry"
:projects :projects
:tasks :tasks
:tags :tags
:clients></TimeEntryCreateModal> :clients
v-model:show="showManualTimeEntryModal"></TimeEntryCreateModal>
<AppLayout title="Dashboard" data-testid="time_view"> <AppLayout title="Dashboard" data-testid="time_view">
<MainContainer <MainContainer
class="pt-5 lg:pt-8 pb-4 lg:pb-6 border-b border-default-background-separator"> class="pt-5 lg:pt-8 pb-4 lg:pb-6 border-b border-default-background-separator">
@@ -141,8 +141,8 @@ function deleteSelected() {
<div class="pb-2 pt-2 lg:pt-0 lg:pl-4 flex justify-center"> <div class="pb-2 pt-2 lg:pt-0 lg:pl-4 flex justify-center">
<SecondaryButton <SecondaryButton
class="w-full text-center flex justify-center" class="w-full text-center flex justify-center"
:icon="PlusIcon"
@click="showManualTimeEntryModal = true" @click="showManualTimeEntryModal = true"
:icon="PlusIcon"
>Manual time entry >Manual time entry
</SecondaryButton> </SecondaryButton>
</div> </div>
@@ -150,9 +150,12 @@ function deleteSelected() {
</MainContainer> </MainContainer>
<TimeEntryMassActionRow <TimeEntryMassActionRow
:selected-time-entries="selectedTimeEntries" :selected-time-entries="selectedTimeEntries"
:enable-estimated-time="isAllowedToPerformPremiumAction()" :enableEstimatedTime="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()" :canCreateProject="canCreateProjects()"
@submit="clearSelectionAndState"
:all-selected="selectedTimeEntries.length === timeEntries.length" :all-selected="selectedTimeEntries.length === timeEntries.length"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"
:delete-selected="deleteSelected" :delete-selected="deleteSelected"
:projects="projects" :projects="projects"
:tasks="tasks" :tasks="tasks"
@@ -168,26 +171,23 @@ function deleteSelected() {
" "
:create-project="createProject" :create-project="createProject"
:create-client="createClient" :create-client="createClient"
:create-tag="createTag" :createTag="createTag"></TimeEntryMassActionRow>
@submit="clearSelectionAndState"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"></TimeEntryMassActionRow>
<TimeEntryGroupedTable <TimeEntryGroupedTable
v-model:selected="selectedTimeEntries" v-model:selected="selectedTimeEntries"
:create-project :createProject
:enable-estimated-time="isAllowedToPerformPremiumAction()" :enableEstimatedTime="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()" :canCreateProject="canCreateProjects()"
:clients :clients
:create-client :createClient
:update-time-entry :updateTimeEntry
:update-time-entries :updateTimeEntries
:delete-time-entries :deleteTimeEntries
:create-time-entry="startTimeEntry" :createTimeEntry="startTimeEntry"
:create-tag :createTag
:projects="projects" :projects="projects"
:tasks="tasks" :tasks="tasks"
:currency="getOrganizationCurrencyString()" :currency="getOrganizationCurrencyString()"
:time-entries="timeEntries" :timeEntries="timeEntries"
:tags="tags"></TimeEntryGroupedTable> :tags="tags"></TimeEntryGroupedTable>
<div v-if="timeEntries.length === 0" class="text-center pt-12"> <div v-if="timeEntries.length === 0" class="text-center pt-12">
<ClockIcon class="w-8 text-icon-default inline pb-2"></ClockIcon> <ClockIcon class="w-8 text-icon-default inline pb-2"></ClockIcon>

File diff suppressed because it is too large Load Diff

View File

@@ -57,13 +57,13 @@
"peerDependencies": { "peerDependencies": {
"@floating-ui/vue": "^1.1.4", "@floating-ui/vue": "^1.1.4",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@vueuse/core": "^12.5.0", "@vueuse/core": "^10.11.0",
"@zodios/core": "^10.9.6", "@zodios/core": "^10.9.6",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"parse-duration": "^2.0.1", "parse-duration": "^1.1.0",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss": "^3.1.0", "tailwindcss": "^3.1.0",
"vue": "^3.5.0", "vue": "^3.4.38",
"vue-tsc": "^2.2.0" "vue-tsc": "^2.0.29"
} }
} }

View File

@@ -4,11 +4,11 @@ import { computed } from 'vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
size?: 'base' | 'large' | 'xlarge'; size: 'base' | 'large' | 'xlarge';
tag?: string; tag: string;
class?: string; class?: string;
color?: string; color: string;
border?: boolean; border: boolean;
}>(), }>(),
{ {
size: 'base', size: 'base',
@@ -30,13 +30,6 @@ const borderClasses = computed(() => {
} }
return ''; return '';
}); });
const tagClasses = computed(() => {
if (props.tag === 'button') {
return 'hover:bg-tertiary';
}
return '';
});
</script> </script>
<template> <template>
@@ -44,10 +37,9 @@ const tagClasses = computed(() => {
:is="tag" :is="tag"
:class=" :class="
twMerge( twMerge(
tagClasses,
badgeClasses[size], badgeClasses[size],
borderClasses, borderClasses,
'rounded transition inline-flex items-center font-semibold text-white disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'rounded inline-flex items-center font-semibold text-white disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
props.class props.class
) )
"> ">

View File

@@ -6,9 +6,9 @@ import { twMerge } from 'tailwind-merge';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
type?: HtmlButtonType; type: HtmlButtonType;
icon?: Component; icon?: Component;
loading?: boolean; loading: boolean;
}>(), }>(),
{ {
type: 'submit', type: 'submit',
@@ -21,15 +21,15 @@ const props = withDefaults(
<button <button
:type="type" :type="type"
:disabled="loading" :disabled="loading"
class="inline-flex items-center px-2 sm:px-3 py-1 sm:py-2 bg-accent-300/10 border border-accent-300/20 rounded-md font-medium text-xs sm:text-sm text-white hover:bg-accent-300/20 active:bg-accent-300/20 focus:outline-none focus-visible:ring-2 focus-visible:border-transparent focus-visible:ring-ring transition ease-in-out duration-150"> class="inline-flex items-center px-2 sm:px-3 py-1 sm:py-2 bg-accent-300/10 border border-accent-300/20 rounded-md font-medium text-xs sm:text-sm text-white hover:bg-accent-300/20 active:bg-accent-300/20 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
<span <span
:class=" :class="
twMerge('flex items-center ', props.icon ? 'space-x-1.5' : '') twMerge('flex items-center ', props.icon ? 'space-x-1.5' : '')
"> ">
<LoadingSpinner v-if="loading"></LoadingSpinner> <LoadingSpinner v-if="loading"></LoadingSpinner>
<component <component
:is="props.icon"
v-if="props.icon && !loading" v-if="props.icon && !loading"
:is="props.icon"
class="text-text-secondary w-4 -ml-0.5 mr-1"></component> class="text-text-secondary w-4 -ml-0.5 mr-1"></component>
<span> <span>
<slot /> <slot />

View File

@@ -6,10 +6,10 @@ import LoadingSpinner from '../LoadingSpinner.vue';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
type?: HtmlButtonType; type: HtmlButtonType;
icon?: Component; icon?: Component;
size?: 'small' | 'base'; size: 'small' | 'base';
loading?: boolean; loading: boolean;
class?: string; class?: string;
}>(), }>(),
{ {
@@ -31,7 +31,7 @@ const sizeClasses = {
:disabled="loading" :disabled="loading"
:class=" :class="
twMerge( twMerge(
'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus-visible:outline-none focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring focus:border-transparent disabled:opacity-25 ease-in-out', 'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus-visible:border-input-border-active focus:outline-none focus:ring-0 disabled:opacity-25 ease-in-out',
sizeClasses[props.size], sizeClasses[props.size],
props.class props.class
) )
@@ -42,8 +42,8 @@ const sizeClasses = {
"> ">
<LoadingSpinner v-if="loading"></LoadingSpinner> <LoadingSpinner v-if="loading"></LoadingSpinner>
<component <component
:is="props.icon"
v-if="props.icon && !loading" v-if="props.icon && !loading"
:is="props.icon"
class="text-text-tertiary w-4 -ml-0.5 mr-1"></component> class="text-text-tertiary w-4 -ml-0.5 mr-1"></component>
<span> <span>
<slot /> <slot />

View File

@@ -12,8 +12,8 @@ defineProps<{
<h3 <h3
class="text-white font-bold text-sm lg:text-base flex items-center space-x-2 lg:space-x-2.5"> class="text-white font-bold text-sm lg:text-base flex items-center space-x-2 lg:space-x-2.5">
<component <component
:is="icon"
v-if="icon" v-if="icon"
:is="icon"
class="w-5 lg:w-6 text-icon-default"></component> class="w-5 lg:w-6 text-icon-default"></component>
<span> <span>
{{ title }} {{ title }}

Some files were not shown because too many files have changed in this diff Show More