Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot]
924d389488 Bump the major-updates group across 1 directory with 12 updates
Bumps the major-updates group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@tanstack/vue-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/vue-query-devtools) | `5.91.0` | `6.1.24` |
| [lucide-vue-next](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next) | `0.487.0` | `1.0.0` |
| [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `2.6.1` | `3.5.0` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.2` | `10.0.1` |
| [@inertiajs/vue3](https://github.com/inertiajs/inertia/tree/HEAD/packages/vue3) | `2.3.13` | `3.0.3` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.7` | `25.6.0` |
| [laravel-vite-plugin](https://github.com/laravel/vite-plugin) | `2.1.0` | `3.0.1` |
| [postcss-import](https://github.com/postcss/postcss-import) | `15.1.0` | `16.1.1` |
| [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-nesting) | `12.1.5` | `14.0.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `3.4.19` | `4.2.4` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.3` | `6.0.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.1` | `8.0.10` |



Updates `@tanstack/vue-query-devtools` from 5.91.0 to 6.1.24
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/vue-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/vue-query-devtools@6.1.24/packages/vue-query-devtools)

Updates `lucide-vue-next` from 0.487.0 to 1.0.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.0.0/packages/lucide-vue-next)

Updates `tailwind-merge` from 2.6.1 to 3.5.0
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v2.6.1...v3.5.0)

Updates `@eslint/js` from 9.39.2 to 10.0.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v10.0.1/packages/js)

Updates `@inertiajs/vue3` from 2.3.13 to 3.0.3
- [Release notes](https://github.com/inertiajs/inertia/releases)
- [Changelog](https://github.com/inertiajs/inertia/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/inertiajs/inertia/commits/v3.0.3/packages/vue3)

Updates `@types/node` from 22.19.7 to 25.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `laravel-vite-plugin` from 2.1.0 to 3.0.1
- [Release notes](https://github.com/laravel/vite-plugin/releases)
- [Changelog](https://github.com/laravel/vite-plugin/blob/3.x/CHANGELOG.md)
- [Upgrade guide](https://github.com/laravel/vite-plugin/blob/3.x/UPGRADE.md)
- [Commits](https://github.com/laravel/vite-plugin/compare/v2.1.0...v3.0.1)

Updates `postcss-import` from 15.1.0 to 16.1.1
- [Release notes](https://github.com/postcss/postcss-import/releases)
- [Changelog](https://github.com/postcss/postcss-import/blob/master/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-import/compare/15.1.0...16.1.1)

Updates `postcss-nesting` from 12.1.5 to 14.0.0
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-nesting/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-nesting)

Updates `tailwindcss` from 3.4.19 to 4.2.4
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/tailwindcss)

Updates `typescript` from 5.9.3 to 6.0.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.3)

Updates `vite` from 7.3.1 to 8.0.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 10.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@inertiajs/vue3"
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@tanstack/vue-query-devtools"
  dependency-version: 6.1.10
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: laravel-vite-plugin
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: lucide-vue-next
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: postcss-import
  dependency-version: 16.1.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: postcss-nesting
  dependency-version: 14.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: tailwind-merge
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: tailwindcss
  dependency-version: 4.2.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: typescript
  dependency-version: 6.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: vite
  dependency-version: 8.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 09:01:37 +00:00
Gregor Vostrak
b73aa543fd Merge commit from fork 2026-04-21 21:12:30 +02:00
Gregor Vostrak
2d6f9e514f add groupSimilarTimeEntries to TimeEntryGroupedTable 2026-04-21 20:44:33 +02:00
5 changed files with 978 additions and 1623 deletions

View File

@@ -629,9 +629,9 @@ class TimeEntryController extends Controller
/** @var Member|null $member */
$member = $request->has('member_id') ? Member::query()->findOrFail($request->input('member_id')) : null;
if ($timeEntry->member->user_id === Auth::id() && ($member === null || $member->user_id === Auth::id())) {
$this->checkPermission($organization, 'time-entries:update:own');
$this->checkPermission($organization, 'time-entries:update:own', $timeEntry);
} else {
$this->checkPermission($organization, 'time-entries:update:all');
$this->checkPermission($organization, 'time-entries:update:all', $timeEntry);
}
if ($timeEntry->end !== null && $request->has('end') && $request->input('end') === null) {

2489
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,26 +19,26 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@inertiajs/vue3": "^2.0.0",
"@eslint/js": "^10.0.1",
"@inertiajs/vue3": "^3.0.3",
"@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/chroma-js": "^3.1.0",
"@types/node": "^22.10.10",
"@types/node": "^25.6.0",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.0",
"autoprefixer": "^10.4.20",
"axios": "^1.6.4",
"eslint-plugin-unused-imports": "^4.1.4",
"laravel-vite-plugin": "^2.1.0",
"laravel-vite-plugin": "^3.0.1",
"openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.13",
"typescript": "^5.7.3",
"vite": "^7.0.0",
"postcss-import": "^16.1.1",
"postcss-nesting": "^14.0.0",
"tailwindcss": "^4.2.4",
"typescript": "^6.0.3",
"vite": "^8.0.10",
"vite-plugin-checker": "^0.12.0",
"vue": "^3.5.0",
"vue-tsc": "^3.0.0"
@@ -51,7 +51,7 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-form": "^1.3.1",
"@tanstack/vue-query": "^5.56.2",
"@tanstack/vue-query-devtools": "^5.58.0",
"@tanstack/vue-query-devtools": "^6.1.24",
"@tanstack/vue-table": "^8.21.2",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0",
@@ -64,12 +64,12 @@
"dayjs": "^1.11.11",
"echarts": "^6.0.0",
"focus-trap": "^8.0.0",
"lucide-vue-next": "^0.487.0",
"lucide-vue-next": "^1.0.0",
"parse-duration": "^2.0.1",
"pinia": "^3.0.0",
"radix-vue": "^1.9.6",
"reka-ui": "^2.8.2",
"tailwind-merge": "^2.6.0",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"vue-echarts": "^8.0.0",
"zod": "^3.23.8"

View File

@@ -20,25 +20,30 @@ const selectedTimeEntries = defineModel<TimeEntry[]>('selected', {
default: [],
});
const props = defineProps<{
timeEntries: TimeEntry[];
projects: Project[];
tasks: Task[];
tags: Tag[];
clients: Client[];
createTag: (name: string) => Promise<Tag | undefined>;
updateTimeEntry: (entry: TimeEntry) => void;
updateTimeEntries: (ids: string[], changes: Partial<TimeEntry>) => void;
deleteTimeEntries: (entries: TimeEntry[]) => void;
createTimeEntry: (entry: Omit<CreateTimeEntryBody, 'member_id'>) => void;
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
currency: string;
organizationBillableRate: number | null;
enableEstimatedTime: boolean;
canCreateProject: boolean;
groupSimilarTimeEntries: boolean;
}>();
const props = withDefaults(
defineProps<{
timeEntries: TimeEntry[];
projects: Project[];
tasks: Task[];
tags: Tag[];
clients: Client[];
createTag: (name: string) => Promise<Tag | undefined>;
updateTimeEntry: (entry: TimeEntry) => void;
updateTimeEntries: (ids: string[], changes: Partial<TimeEntry>) => void;
deleteTimeEntries: (entries: TimeEntry[]) => void;
createTimeEntry: (entry: Omit<CreateTimeEntryBody, 'member_id'>) => void;
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
currency: string;
organizationBillableRate: number | null;
enableEstimatedTime: boolean;
canCreateProject: boolean;
groupSimilarTimeEntries?: boolean;
}>(),
{
groupSimilarTimeEntries: true,
}
);
const groupedTimeEntries = computed(() => {
const groupedEntriesByDay: Record<string, TimeEntry[]> = {};

View File

@@ -2490,6 +2490,47 @@ class TimeEntryEndpointTest extends ApiEndpointTestAbstract
$response->assertForbidden();
}
public function test_update_endpoint_fails_if_time_entry_belongs_to_different_organization_than_url_even_with_update_all_permission(): void
{
// Arrange
// Attacker: has `time-entries:update:all` in their own organization (orgA).
$data = $this->createUserWithPermission([
'time-entries:update:all',
'projects:view:all',
]);
$attackerProject = Project::factory()->forOrganization($data->organization)->create();
// Victim: entirely separate organization (orgB). Attacker has NO membership in orgB.
$victimOrgData = $this->createUserWithPermission([], true);
$victimTimeEntry = TimeEntry::factory()
->forOrganization($victimOrgData->organization)
->forMember($victimOrgData->ownerMember)
->create([
'description' => 'victim-original',
'project_id' => null,
'task_id' => null,
]);
Passport::actingAs($data->user);
// Act: PUT to /organizations/{orgA}/time-entries/{victim_uuid} — URL org is attacker's
// own org, but the route-bound time entry belongs to orgB.
$response = $this->putJson(route('api.v1.time-entries.update', [$data->organization->getKey(), $victimTimeEntry->getKey()]), [
'description' => 'attacker-overwrite',
'project_id' => $attackerProject->getKey(),
]);
// Assert: must be rejected. Before the fix this returned 200 and rewrote the
// victim row with attacker's project_id while keeping organization_id = orgB.
$response->assertForbidden();
$this->assertDatabaseHas(TimeEntry::class, [
'id' => $victimTimeEntry->getKey(),
'organization_id' => $victimOrgData->organization->getKey(),
'description' => 'victim-original',
'project_id' => null,
]);
}
public function test_update_endpoint_fails_if_user_has_no_permission_to_update_time_entries_for_other_users_in_organization(): void
{
// Arrange