Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
7a51fca
only show Weekly Billable Amount of current organization on dashboard…
Onatcer Dec 2, 2025
814d539
move rangecalendar, popover and daterangepicker to ui package
Onatcer Dec 2, 2025
3acf9b8
add support for window activities in the calendar view plugin
Onatcer Dec 3, 2025
513b204
move TimezonMismatchModal to ui package
Onatcer Dec 4, 2025
0691fe1
add direct axios dependency to package, bump package versions
Onatcer Dec 9, 2025
de97d15
add tailwind theme and css variables to files export, bump ui package…
Onatcer Dec 9, 2025
743c649
restrict time entries create endpoints for employees to only projects…
Onatcer Dec 16, 2025
db57055
add filters and sorting to projects table
Onatcer Jan 7, 2026
d27f023
refactor BaseFilterBadge to use DropdownMenuTrigger directly and avoi…
Onatcer Jan 8, 2026
9bbbfdf
improve visual hierarchy in time view
Onatcer Jan 8, 2026
00d9d14
improve time entry heading contrast in light mode
Onatcer Jan 8, 2026
5a05ee3
change dashboard card colors and input background colors
Onatcer Jan 9, 2026
19064cd
make time entry calendar use seconds as a duration basis, fixes #996
Onatcer Jan 14, 2026
f242ce4
change rounding up on boundaries so it does not round up but keeps th…
Onatcer Jan 14, 2026
51af3db
Add test to TimeEntryEndpointTest
korridor Jan 15, 2026
0a6bde8
upgrade inertia v2; add prefetching; migrate queries to tanstack query
Onatcer Jan 9, 2026
81d9561
refactor timeentries queries and mutations, improve activitygraph, ad…
Onatcer Jan 14, 2026
6555bca
use tanstack query in ProjectMultiselectDropdown, ClientTableRow and …
Onatcer Jan 14, 2026
b0d28f2
fix e2e project filtering in reporting e2e test
Onatcer Jan 14, 2026
3c2ea0e
load time entries above pagination limit for calendar, fixes #995
Onatcer Jan 14, 2026
bfc3697
remove redundant projects pinia store after tanstack query migration
Onatcer Jan 14, 2026
8c16302
add outline and secondary variants to TimeTrackerStartStop button to …
Onatcer Jan 15, 2026
70b78e4
add command palette
Onatcer Jan 27, 2026
c6c1434
fix: display custom billable rate correctly on project detail page
Onatcer Jan 27, 2026
3707f24
fix styling inconsistencies
Onatcer Jan 27, 2026
756b423
migrate select/multiselect components to Radix Vue primitives
Onatcer Feb 1, 2026
bc562bf
refactor: extract ReportingFilterBar and migrate reporting to TanStac…
Onatcer Feb 2, 2026
dd75a80
add no project, no task, no client, no task, no tag support to the API
Onatcer Feb 2, 2026
cb7baef
Update openapi api client spec
Onatcer Feb 2, 2026
73b4d66
Add reporting e2e helpers and detailed tests
Onatcer Feb 2, 2026
b2af9c6
Add client_ids filter to time entry export
Onatcer Feb 2, 2026
7efb7e6
Enable npm workspaces and update dependencies
Onatcer Feb 2, 2026
ff06d4d
fix Y-Label ui regression from echarts update
Onatcer Feb 2, 2026
9379c19
Add Mailpit SMTP and refine Playwright tests
Onatcer Feb 2, 2026
28fc324
Allow NONE filter value to shared reports and add shared-report tests
Onatcer Feb 2, 2026
537a023
Add calendar query prefetch
Onatcer Feb 3, 2026
73c92fa
fix responsive issues in timetracker recently tracked entries dropdown
Onatcer Feb 3, 2026
bd2d57d
Improve Time page responsiveness and compact tags, fixes #896
Onatcer Feb 3, 2026
531443f
fix admin panel time entry save and update, fixes #997
Onatcer Feb 4, 2026
6804eb0
Make sure that time entry billable status updates when project changes,
Onatcer Feb 4, 2026
9832c68
fix desync of checkboxes on the reporting detailed page, fixes #892
Onatcer Feb 4, 2026
a154293
migrate datepickers to shadcn, Fixes #877, #807
Onatcer Feb 5, 2026
c17d87b
Allow updating public_until on already-public reports
Onatcer Feb 5, 2026
f75a19b
improve time estimate input, responsive time entry create modal fixes,
Onatcer Feb 6, 2026
125f6f0
Expand e2e test coverage migrate to API-based data setup
Onatcer Feb 9, 2026
15e61e9
Add Field component system and migrate UI
Onatcer Feb 10, 2026
562ee23
Add Euro Symbol as Billable Icon when EUR is the organization currency.
Onatcer Feb 10, 2026
f3217ba
Add Tag Edit Modal and UI
Onatcer Feb 10, 2026
e45662c
add sharding for e2e tests in CI
Onatcer Feb 10, 2026
8be5535
add e2e tests for employee restrictions
Onatcer Feb 10, 2026
334a980
use frankenphp in the playwright tests CI to handle parallel requests
Onatcer Feb 10, 2026
474b294
fix reporting tab selectors in e2e test
Onatcer Feb 10, 2026
a9e03f3
responsive time entry modal fixes
Onatcer Feb 10, 2026
b68d68a
make sure that 404 current time entry requests do not override local
Onatcer Feb 10, 2026
2d9f333
improve format settings e2e test consistency; improve euro icon sizing
Onatcer Feb 10, 2026
c78c681
Conditionally show cost column in report tables; Task/Project Modal
Onatcer Feb 11, 2026
685cc29
improve layout consistency between project and project show page, fix
Onatcer Feb 11, 2026
b258717
improve reporting page responsive layout; standardize button sizing;
Onatcer Feb 12, 2026
052424a
add animation to the mobile sidebar
Onatcer Feb 12, 2026
6f207a4
hide "All time entries are loaded" when no time entries are created yet
Onatcer Feb 12, 2026
0e63ecb
improve timetracker on mobile; fix select all checkbox with 0 time
Onatcer Feb 12, 2026
38457ca
make sure e2e tests use the visible timer button only
Onatcer Feb 12, 2026
6629482
set maximum-scale=1 to prevent weird ios zoom behaviours
Onatcer Feb 12, 2026
cfbfbd4
remove no tags option from timetracker tag dropdown
Onatcer Feb 13, 2026
22cf7cf
limit initially loaded time entries on the time page to 50
Onatcer Feb 13, 2026
c5c1a7a
add project and task prefetches to the dashboard prefetch
Onatcer Feb 13, 2026
2c222f3
fix time loading spinner flashing “no time entries” on direct load
Onatcer Feb 13, 2026
2c60d04
override current_team_id in makeMemberToPlaceholder to avoid fk const…
Onatcer Feb 16, 2026
6dc517e
make sure days with low tracked time are clearly distinguished from n…
Onatcer Feb 16, 2026
8107c6a
improve activitygraphcard chart coloring steps
Onatcer Feb 16, 2026
ed2b747
clear inertia cache on organization change to fix wrongly loaded stal…
Onatcer Feb 16, 2026
b579ed1
bump ui package version to 0.0.16
Onatcer Feb 16, 2026
7f145cf
make sure cost column shows in shared report view, #1019
Onatcer Feb 17, 2026
f1d001e
add lazy loading to modals and dropdowns to improve time page render …
Onatcer Feb 17, 2026
435522b
make OrganizationPolicy use “organizations:update” to remove jetstrea…
Onatcer Feb 17, 2026
147514a
convert billable query string to boolean for shared report + e2e test…
Onatcer Feb 17, 2026
1afc165
cleanup postcss config dependency in ui package
Onatcer Feb 17, 2026
0fc3253
update query keys to include org id, preventing stale data after orga…
Onatcer Feb 18, 2026
88c0c33
add project progress sorting and fix direction ui for number based
Onatcer Feb 18, 2026
cf98fab
add table sorting to members, clients and tags table
Onatcer Feb 18, 2026
26524c5
fix member edit modal ui regression from field component migration
Onatcer Feb 18, 2026
864f41b
fix project member query invalidations after update, query key change…
Onatcer Feb 18, 2026
eed638d
add default sorting to task, project, member, invitation, api token e…
Onatcer Feb 18, 2026
556bbed
add dynamic loading of paginated endpoints above page_limit
Onatcer Feb 18, 2026
f880f9f
fix firefox flaky input in e2e test
Onatcer Feb 18, 2026
d2f3fe4
add missing query invalidation after report create
Onatcer Feb 18, 2026
2a0b8d3
add calendar settings + custom visual snapping
Onatcer Feb 23, 2026
8973be9
filament minor version update
Onatcer Feb 24, 2026
cae41e4
improve visual snapping boundaries
Onatcer Feb 24, 2026
c60cff0
fix calendar flickering on move for non-aligned entries
Onatcer Feb 24, 2026
f582ada
fix time entries incorrectly not updating in calendar
Onatcer Feb 24, 2026
84c4750
Add warning for AI slop pull requests
Onatcer Feb 27, 2026
be80912
use api tokens to create e2e test data
Onatcer Mar 2, 2026
6962b66
add retries to api data token setup and xsrf token fallback
Onatcer Mar 2, 2026
97f4bce
bump retries and wait for networkidle in retry
Onatcer Mar 2, 2026
bd817db
only use xsrf token for organization requests
Onatcer Mar 2, 2026
ba32be0
update npm dependencies
Onatcer Mar 2, 2026
6218ffc
update composer dependencies
Onatcer Mar 3, 2026
195ed51
wip
StanBarrows Mar 16, 2026
46711a5
wip
StanBarrows Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 9 additions & 1 deletion .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ SESSION_DRIVER=database
SESSION_LIFETIME=120

# Mail
MAIL_MAILER=log
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="no-reply@solidtime.test"
MAIL_FROM_NAME="solidtime"
MAIL_REPLY_TO_ADDRESS="hello@solidtime.test"
Expand All @@ -56,3 +61,6 @@ TELESCOPE_ENABLED=false

# Services
GOTENBERG_URL=http://0.0.0.0:3000

# Octane
OCTANE_SERVER=frankenphp
63 changes: 56 additions & 7 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]

services:
mailpit:
image: 'axllent/mailpit:latest'
ports:
- 1025:1025
- 8025:8025
pgsql_test:
image: postgres:15
env:
Expand Down Expand Up @@ -57,22 +65,63 @@ jobs:
- name: "Build Frontend"
run: npm run build

- name: "Run Laravel Server"
run: php artisan serve > /dev/null 2>&1 &
- name: "Install FrankenPHP"
run: |
ARCH="$(uname -m)"
curl -fsSL "https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-${ARCH}" -o /usr/local/bin/frankenphp
chmod +x /usr/local/bin/frankenphp

- name: "Run Laravel Octane Server"
run: php artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000 --workers=4 --max-requests=500 > /dev/null 2>&1 &
env:
OCTANE_SERVER: frankenphp

- name: "Install Playwright Browsers"
run: npx playwright install --with-deps

- name: "Run Playwright tests"
run: npx playwright test
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
env:
PLAYWRIGHT_BASE_URL: 'http://127.0.0.1:8000'
MAILPIT_BASE_URL: 'http://localhost:8025'

- name: "Upload test results"
- name: "Upload blob report"
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/
retention-days: 30
name: blob-report-${{ matrix.shardIndex }}
path: blob-report/
retention-days: 7

merge-reports:
if: always()
needs: [test]
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v4

- name: "Setup node"
uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: "Install dependencies"
run: npm ci

- name: "Download blob reports"
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true

- name: "Merge reports"
run: npx playwright merge-reports --reporter html ./all-blob-reports

- name: "Upload merged HTML report"
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ If you have a **feature request**, please [**create a discussion**](https://gith

Please open an issue or start a discussion and wait for approval before submitting a pull request. This does not apply to tiny fixes or changes however, please keep in mind that we might not merge PRs for various reasons.

**If you submit an AI slop pull request (especially without following the proper procedure), you will be banned from future contributions to solidtime.**

Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) before sumbitting a Pull Request.

We do accept contributions in the [documentation repository](https://github.com/solidtime-io/docs) f.e. to add new self-hosting guides.
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/FailedJobResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static function form(Form $form): Form
TextInput::make('queue')->disabled(),

// make text a little bit smaller because often a complete Stack Trace is shown:
TextArea::make('exception')->disabled()->columnSpan(4)->extraInputAttributes(['style' => 'font-size: 80%;']),
Textarea::make('exception')->disabled()->columnSpan(4)->extraInputAttributes(['style' => 'font-size: 80%;']),
PrettyJsonField::make('payload')->disabled()->columnSpan(4),
])->columns(4);
}
Expand Down
2 changes: 1 addition & 1 deletion app/Filament/Resources/OrganizationInvitationResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static function form(Form $form): Form
->required(),
Select::make('role')
->options(Role::class),
Forms\Components\Select::make('organization_id')
Select::make('organization_id')
->label('Organization')
->relationship(name: 'organization', titleAttribute: 'name')
->searchable(['name'])
Expand Down
16 changes: 8 additions & 8 deletions app/Filament/Resources/OrganizationResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static function form(Form $form): Form
->label('Is personal?')
->hiddenOn(['create'])
->required(),
Forms\Components\Select::make('user_id')
Select::make('user_id')
->label('Owner')
->relationship(name: 'owner', titleAttribute: 'email')
->searchable(['name', 'email'])
Expand All @@ -76,7 +76,7 @@ public static function form(Form $form): Form
Select::make('time_format')
->options(TimeFormat::toSelectArray())
->required(),
Forms\Components\Select::make('currency')
Select::make('currency')
->label('Currency')
->options(function (): array {
$currencies = ISOCurrencyProvider::getInstance()->getAvailableCurrencies();
Expand Down Expand Up @@ -114,22 +114,22 @@ public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\IconColumn::make('personal_team')
->boolean()
->label('Is personal?')
->sortable(),
Tables\Columns\TextColumn::make('owner.email')
TextColumn::make('owner.email')
->sortable(),
Tables\Columns\TextColumn::make('currency'),
TextColumn::make('currency'),
TextColumn::make('billable_rate')
->money(fn (Organization $resource) => $resource->currency, divideBy: 100),
Tables\Columns\TextColumn::make('created_at')
TextColumn::make('created_at')
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Expand Down Expand Up @@ -223,7 +223,7 @@ public static function table(Table $table): Table

return $select;
}),
Forms\Components\Select::make('timezone')
Select::make('timezone')
->label('Timezone')
->options(fn (): array => app(TimezoneService::class)->getSelectOptions())
->searchable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public function table(Table $table): Table
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name'),
Tables\Columns\TextColumn::make('role'),
TextColumn::make('name'),
TextColumn::make('role'),
TextColumn::make('billable_rate')
->money($organization->currency, divideBy: 100),
])
->headerActions([
Tables\Actions\AttachAction::make()
AttachAction::make()
->recordTitle(fn (User $record): string => "{$record->name} ({$record->email})")
->form(fn (AttachAction $action): array => [
$action->getRecordSelect(),
Expand Down
12 changes: 6 additions & 6 deletions app/Filament/Resources/ReportResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public static function form(Form $form): Form
return $record->getRawOriginal('properties');
})
->disabled(),
Forms\Components\DateTimePicker::make('created_at')
DateTimePicker::make('created_at')
->label('Created At')
->hiddenOn(['create'])
->disabled(),
Forms\Components\DateTimePicker::make('updated_at')
DateTimePicker::make('updated_at')
->label('Updated At')
->hiddenOn(['create'])
->disabled(),
Expand All @@ -78,10 +78,10 @@ public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('description')
TextColumn::make('description')
->searchable()
->sortable(),
ToggleColumn::make('is_public')
Expand All @@ -90,10 +90,10 @@ public static function table(Table $table): Table
TextColumn::make('organization.name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
TextColumn::make('created_at')
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Expand Down
24 changes: 17 additions & 7 deletions app/Filament/Resources/TimeEntryResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Filament\Resources;

use App\Filament\Resources\TimeEntryResource\Pages;
use App\Models\Member;
use App\Models\TimeEntry;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Select;
Expand All @@ -16,6 +17,7 @@
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;

class TimeEntryResource extends Resource
{
Expand Down Expand Up @@ -51,15 +53,23 @@ public static function form(Form $form): Form
->rules([
'after_or_equal:start',
]),
Select::make('user_id')
->relationship(name: 'user', titleAttribute: 'email')
->searchable(['name', 'email'])
Select::make('member_id')
->relationship(
name: 'member',
titleAttribute: 'id',
modifyQueryUsing: fn (Builder $query) => $query->with(['user', 'organization'])
)
->getOptionLabelFromRecordUsing(fn (Member $record): string => $record->user->email.' ('.$record->organization->name.')')
->searchable()
->required(),
Select::make('project_id')
->relationship(name: 'project', titleAttribute: 'name')
->searchable(['name'])
->nullable(),
// TODO
Select::make('task_id')
->relationship(name: 'task', titleAttribute: 'name')
->searchable(['name'])
->nullable(),
]);
}

Expand All @@ -83,11 +93,11 @@ public static function table(Table $table): Table
($record->end?->toDateTimeString('minute') ?? '...').')';
})
->label('Time'),
Tables\Columns\TextColumn::make('organization.name')
TextColumn::make('organization.name')
->sortable(),
Tables\Columns\TextColumn::make('created_at')
TextColumn::make('created_at')
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
TextColumn::make('updated_at')
->sortable(),
])
->filters([
Expand Down
19 changes: 19 additions & 0 deletions app/Filament/Resources/TimeEntryResource/Pages/CreateTimeEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,28 @@
namespace App\Filament\Resources\TimeEntryResource\Pages;

use App\Filament\Resources\TimeEntryResource;
use App\Models\Member;
use Filament\Resources\Pages\CreateRecord;

class CreateTimeEntry extends CreateRecord
{
protected static string $resource = TimeEntryResource::class;

/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function mutateFormDataBeforeCreate(array $data): array
{
if (isset($data['member_id'])) {
/** @var Member|null $member */
$member = Member::query()->find($data['member_id']);
if ($member !== null) {
$data['user_id'] = $member->user_id;
$data['organization_id'] = $member->organization_id;
}
}

return $data;
}
}
19 changes: 19 additions & 0 deletions app/Filament/Resources/TimeEntryResource/Pages/EditTimeEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Filament\Resources\TimeEntryResource\Pages;

use App\Filament\Resources\TimeEntryResource;
use App\Models\Member;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;

Expand All @@ -19,4 +20,22 @@ protected function getHeaderActions(): array
->icon('heroicon-m-trash'),
];
}

/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
protected function mutateFormDataBeforeSave(array $data): array
{
if (isset($data['member_id'])) {
/** @var Member|null $member */
$member = Member::query()->find($data['member_id']);
if ($member !== null) {
$data['user_id'] = $member->user_id;
$data['organization_id'] = $member->organization_id;
}
}

return $data;
}
}
6 changes: 3 additions & 3 deletions app/Filament/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@ public static function form(Form $form): Form
return $form
->columns(1)
->schema([
Forms\Components\TextInput::make('id')
TextInput::make('id')
->label('ID')
->disabled()
->visibleOn(['update', 'show'])
->readOnly()
->maxLength(255),
Forms\Components\TextInput::make('name')
TextInput::make('name')
->label('Name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
TextInput::make('email')
->label('Email')
->required()
->rules($record?->is_placeholder ? [] : [
Expand Down
1 change: 1 addition & 0 deletions app/Http/Controllers/Api/V1/ApiTokenController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function index(): ApiTokenCollection
/** @var Builder<Client> $query */
$query->whereJsonContains('grant_types', 'personal_access');
})
->orderBy('created_at', 'desc')
->get();

return new ApiTokenCollection($tokens);
Expand Down
Loading
Loading