mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
Compare commits
2 Commits
7e993a9249
...
d0f2ee7d7e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f2ee7d7e | ||
|
|
767eb00f7b |
16
app/Enums/TagMatchType.php
Normal file
16
app/Enums/TagMatchType.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Datomatic\LaravelEnumHelper\LaravelEnumHelper;
|
||||
|
||||
enum TagMatchType: string
|
||||
{
|
||||
use LaravelEnumHelper;
|
||||
|
||||
case Contains = 'contains';
|
||||
|
||||
case NotContains = 'not_contains';
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class ReportController extends Controller
|
||||
$filter->addBillable($properties->billable);
|
||||
$filter->addMemberIdsFilter($properties->memberIds?->toArray());
|
||||
$filter->addProjectIdsFilter($properties->projectIds?->toArray());
|
||||
$filter->addTagIdsFilter($properties->tagIds?->toArray(), $properties->tagFilter);
|
||||
$filter->addTagIdsFilter($properties->tagIds?->toArray(), $properties->tagMatchType);
|
||||
$filter->addTaskIdsFilter($properties->taskIds?->toArray());
|
||||
$filter->addClientIdsFilter($properties->clientIds?->toArray());
|
||||
$timeEntriesQuery = $filter->get();
|
||||
|
||||
@@ -96,7 +96,7 @@ class ReportController extends Controller
|
||||
$properties->setClientIds($request->input('properties.client_ids', null));
|
||||
$properties->setProjectIds($request->input('properties.project_ids', null));
|
||||
$properties->setTagIds($request->input('properties.tag_ids', null));
|
||||
$properties->setTagFilter($request->input('properties.tag_filter', null));
|
||||
$properties->setTagMatchType($request->getPropertyTagMatchType());
|
||||
$properties->setTaskIds($request->input('properties.task_ids', null));
|
||||
$properties->weekStart = $request->has('properties.week_start') ? Weekday::from($request->input('properties.week_start')) : $user->week_start;
|
||||
$timezone = $user->timezone;
|
||||
|
||||
@@ -203,7 +203,7 @@ class TimeEntryController extends Controller
|
||||
$filter->addMemberIdFilter($member);
|
||||
$filter->addMemberIdsFilter($request->input('member_ids'));
|
||||
$filter->addProjectIdsFilter($request->input('project_ids'));
|
||||
$filter->addTagIdsFilter($request->input('tag_ids'), $request->input('tag_filter'));
|
||||
$filter->addTagIdsFilter($request->input('tag_ids'), $request->getTagMatchType());
|
||||
$filter->addTaskIdsFilter($request->input('task_ids'));
|
||||
$filter->addClientIdsFilter($request->input('client_ids'));
|
||||
$filter->addBillableFilter($request->input('billable'));
|
||||
@@ -559,7 +559,7 @@ class TimeEntryController extends Controller
|
||||
$filter->addMemberIdFilter($member);
|
||||
$filter->addMemberIdsFilter($request->input('member_ids'));
|
||||
$filter->addProjectIdsFilter($request->input('project_ids'));
|
||||
$filter->addTagIdsFilter($request->input('tag_ids'), $request->input('tag_filter'));
|
||||
$filter->addTagIdsFilter($request->input('tag_ids'), $request->getTagMatchType());
|
||||
$filter->addTaskIdsFilter($request->input('task_ids'));
|
||||
$filter->addClientIdsFilter($request->input('client_ids'));
|
||||
$filter->addBillableFilter($request->input('billable'));
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\Report;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryAggregationType;
|
||||
use App\Enums\TimeEntryAggregationTypeInterval;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
@@ -124,10 +125,10 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
}
|
||||
},
|
||||
],
|
||||
'properties.tag_filter' => [
|
||||
'properties.tag_match_type' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'in:'.TimeEntryFilter::TAG_FILTER_CONTAINS.','.TimeEntryFilter::TAG_FILTER_NOT_CONTAINS,
|
||||
Rule::enum(TagMatchType::class),
|
||||
],
|
||||
'properties.task_ids' => [
|
||||
'nullable',
|
||||
@@ -254,6 +255,15 @@ class ReportStoreRequest extends BaseFormRequest
|
||||
return TimeEntryAggregationTypeInterval::from($this->input('properties.history_group'));
|
||||
}
|
||||
|
||||
public function getPropertyTagMatchType(): ?TagMatchType
|
||||
{
|
||||
if (! $this->has('properties.tag_match_type') || $this->input('properties.tag_match_type') === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TagMatchType::from($this->input('properties.tag_match_type'));
|
||||
}
|
||||
|
||||
public function getPropertyRoundingType(): ?TimeEntryRoundingType
|
||||
{
|
||||
if (! $this->has('properties.rounding_type') || $this->input('properties.rounding_type') === null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Enums\ExportFormat;
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryAggregationType;
|
||||
use App\Enums\TimeEntryAggregationTypeInterval;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
@@ -139,9 +140,9 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
'tag_filter' => [
|
||||
'tag_match_type' => [
|
||||
'string',
|
||||
'in:'.TimeEntryFilter::TAG_FILTER_CONTAINS.','.TimeEntryFilter::TAG_FILTER_NOT_CONTAINS,
|
||||
Rule::enum(TagMatchType::class),
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -250,6 +251,15 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
return ExportFormat::from($this->validated('format'));
|
||||
}
|
||||
|
||||
public function getTagMatchType(): ?TagMatchType
|
||||
{
|
||||
if (! $this->has('tag_match_type') || $this->validated('tag_match_type') === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TagMatchType::from($this->validated('tag_match_type'));
|
||||
}
|
||||
|
||||
public function getRoundingType(): ?TimeEntryRoundingType
|
||||
{
|
||||
if (! $this->has('rounding_type') || $this->validated('rounding_type') === null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryAggregationType;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
use App\Http\Requests\V1\BaseFormRequest;
|
||||
@@ -125,9 +126,9 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
'tag_filter' => [
|
||||
'tag_match_type' => [
|
||||
'string',
|
||||
'in:'.TimeEntryFilter::TAG_FILTER_CONTAINS.','.TimeEntryFilter::TAG_FILTER_NOT_CONTAINS,
|
||||
Rule::enum(TagMatchType::class),
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -212,6 +213,15 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
return $this->input('end') !== null ? Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $this->input('end'), 'UTC') : null;
|
||||
}
|
||||
|
||||
public function getTagMatchType(): ?TagMatchType
|
||||
{
|
||||
if (! $this->has('tag_match_type') || $this->validated('tag_match_type') === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TagMatchType::from($this->validated('tag_match_type'));
|
||||
}
|
||||
|
||||
public function getRoundingType(): ?TimeEntryRoundingType
|
||||
{
|
||||
if (! $this->has('rounding_type') || $this->validated('rounding_type') === null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Enums\ExportFormat;
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
use App\Models\Client;
|
||||
use App\Models\Member;
|
||||
@@ -110,9 +111,9 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
'tag_filter' => [
|
||||
'tag_match_type' => [
|
||||
'string',
|
||||
'in:'.TimeEntryFilter::TAG_FILTER_CONTAINS.','.TimeEntryFilter::TAG_FILTER_NOT_CONTAINS,
|
||||
Rule::enum(TagMatchType::class),
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -219,6 +220,15 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
return ExportFormat::from($this->validated('format'));
|
||||
}
|
||||
|
||||
public function getTagMatchType(): ?TagMatchType
|
||||
{
|
||||
if (! $this->has('tag_match_type') || $this->validated('tag_match_type') === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TagMatchType::from($this->validated('tag_match_type'));
|
||||
}
|
||||
|
||||
public function getRoundingType(): ?TimeEntryRoundingType
|
||||
{
|
||||
if (! $this->has('rounding_type') || $this->validated('rounding_type') === null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
use App\Http\Requests\V1\BaseFormRequest;
|
||||
use App\Models\Client;
|
||||
@@ -103,9 +104,9 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
'tag_filter' => [
|
||||
'tag_match_type' => [
|
||||
'string',
|
||||
'in:'.TimeEntryFilter::TAG_FILTER_CONTAINS.','.TimeEntryFilter::TAG_FILTER_NOT_CONTAINS,
|
||||
Rule::enum(TagMatchType::class),
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -194,6 +195,15 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
return $this->has('offset') ? (int) $this->validated('offset', 0) : 0;
|
||||
}
|
||||
|
||||
public function getTagMatchType(): ?TagMatchType
|
||||
{
|
||||
if (! $this->has('tag_match_type') || $this->validated('tag_match_type') === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TagMatchType::from($this->validated('tag_match_type'));
|
||||
}
|
||||
|
||||
public function getRoundingType(): ?TimeEntryRoundingType
|
||||
{
|
||||
if (! $this->has('rounding_type') || $this->validated('rounding_type') === null) {
|
||||
|
||||
@@ -56,8 +56,8 @@ class DetailedReportResource extends BaseResource
|
||||
'project_ids' => $this->resource->properties->projectIds?->toArray(),
|
||||
/** @var array<string>|null $tags_ids Filter by tag IDs, tag IDs are OR combined */
|
||||
'tag_ids' => $this->resource->properties->tagIds?->toArray(),
|
||||
/** @var string|null $tag_filter Tag filter mode */
|
||||
'tag_filter' => $this->resource->properties->tagFilter,
|
||||
/** @var string|null $tag_match_type Tag match type */
|
||||
'tag_match_type' => $this->resource->properties->tagMatchType?->value,
|
||||
/** @var array<string>|null $task_ids Filter by task IDs, task IDs are OR combined */
|
||||
'task_ids' => $this->resource->properties->taskIds?->toArray(),
|
||||
/** @var string|null $rounding_type Rounding type for time entries */
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Dto;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryAggregationType;
|
||||
use App\Enums\TimeEntryAggregationTypeInterval;
|
||||
use App\Enums\TimeEntryRoundingType;
|
||||
@@ -56,7 +57,7 @@ class ReportPropertiesDto implements Castable
|
||||
*/
|
||||
public ?Collection $tagIds = null;
|
||||
|
||||
public ?string $tagFilter = null;
|
||||
public ?TagMatchType $tagMatchType = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, string>|null
|
||||
@@ -117,7 +118,7 @@ class ReportPropertiesDto implements Castable
|
||||
$dto->clientIds = $data->clientIds !== null ? ReportPropertiesDto::idArrayToCollection($data->clientIds) : null;
|
||||
$dto->projectIds = $data->projectIds !== null ? ReportPropertiesDto::idArrayToCollection($data->projectIds) : null;
|
||||
$dto->tagIds = $data->tagIds !== null ? ReportPropertiesDto::idArrayToCollection($data->tagIds) : null;
|
||||
$dto->tagFilter = isset($data->tagFilter) ? ReportPropertiesDto::tagFilterValue($data->tagFilter) : null;
|
||||
$dto->tagMatchType = isset($data->tagMatchType) ? TagMatchType::from($data->tagMatchType) : null;
|
||||
$dto->taskIds = $data->taskIds ? ReportPropertiesDto::idArrayToCollection($data->taskIds) : null;
|
||||
$dto->group = TimeEntryAggregationType::from($data->group);
|
||||
$dto->subGroup = TimeEntryAggregationType::from($data->subGroup);
|
||||
@@ -147,7 +148,7 @@ class ReportPropertiesDto implements Castable
|
||||
'clientIds' => $value->clientIds?->toArray(),
|
||||
'projectIds' => $value->projectIds?->toArray(),
|
||||
'tagIds' => $value->tagIds?->toArray(),
|
||||
'tagFilter' => $value->tagFilter,
|
||||
'tagMatchType' => $value->tagMatchType?->value,
|
||||
'taskIds' => $value->taskIds?->toArray(),
|
||||
'group' => $value->group->value,
|
||||
'subGroup' => $value->subGroup->value,
|
||||
@@ -188,24 +189,6 @@ class ReportPropertiesDto implements Castable
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 'contains'|'not_contains'|null
|
||||
*/
|
||||
public static function tagFilterValue(mixed $tagFilter): ?string
|
||||
{
|
||||
if ($tagFilter === null) {
|
||||
return null;
|
||||
}
|
||||
if (! is_string($tagFilter)) {
|
||||
throw new \InvalidArgumentException('The given tag filter is not a string');
|
||||
}
|
||||
if (! in_array($tagFilter, [TimeEntryFilter::TAG_FILTER_CONTAINS, TimeEntryFilter::TAG_FILTER_NOT_CONTAINS], true)) {
|
||||
throw new \InvalidArgumentException('The given tag filter is not valid');
|
||||
}
|
||||
|
||||
return $tagFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $memberIds
|
||||
*/
|
||||
@@ -238,9 +221,9 @@ class ReportPropertiesDto implements Castable
|
||||
$this->tagIds = $tagIds !== null ? ReportPropertiesDto::idArrayToCollection($tagIds) : null;
|
||||
}
|
||||
|
||||
public function setTagFilter(mixed $tagFilter): void
|
||||
public function setTagMatchType(?TagMatchType $tagMatchType): void
|
||||
{
|
||||
$this->tagFilter = ReportPropertiesDto::tagFilterValue($tagFilter);
|
||||
$this->tagMatchType = $tagMatchType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Models\Member;
|
||||
use App\Models\TimeEntry;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -14,10 +15,6 @@ class TimeEntryFilter
|
||||
{
|
||||
public const string NONE_VALUE = 'none';
|
||||
|
||||
public const string TAG_FILTER_CONTAINS = 'contains';
|
||||
|
||||
public const string TAG_FILTER_NOT_CONTAINS = 'not_contains';
|
||||
|
||||
/**
|
||||
* @var Builder<TimeEntry>
|
||||
*/
|
||||
@@ -196,18 +193,12 @@ class TimeEntryFilter
|
||||
/**
|
||||
* @param array<string>|null $tagIds
|
||||
*/
|
||||
public function addTagIdsFilter(?array $tagIds, ?string $tagFilter = self::TAG_FILTER_CONTAINS): self
|
||||
public function addTagIdsFilter(?array $tagIds, ?TagMatchType $tagMatchType = TagMatchType::Contains): self
|
||||
{
|
||||
if ($tagIds === null) {
|
||||
return $this;
|
||||
}
|
||||
if ($tagFilter === null) {
|
||||
$tagFilter = self::TAG_FILTER_CONTAINS;
|
||||
}
|
||||
if (! in_array($tagFilter, [self::TAG_FILTER_CONTAINS, self::TAG_FILTER_NOT_CONTAINS], true)) {
|
||||
Log::warning('Invalid tag filter value', ['value' => $tagFilter]);
|
||||
$tagFilter = self::TAG_FILTER_CONTAINS;
|
||||
}
|
||||
$tagMatchType ??= TagMatchType::Contains;
|
||||
$includeNone = in_array(self::NONE_VALUE, $tagIds, true);
|
||||
$tagIds = array_values(array_filter($tagIds, fn (string $id): bool => $id !== self::NONE_VALUE));
|
||||
// An empty selection (no tag IDs and not filtering for "none") is no constraint, so apply nothing.
|
||||
@@ -227,7 +218,7 @@ class TimeEntryFilter
|
||||
}
|
||||
};
|
||||
|
||||
if ($tagFilter === self::TAG_FILTER_NOT_CONTAINS) {
|
||||
if ($tagMatchType === TagMatchType::NotContains) {
|
||||
$this->builder->where(function (Builder $builder) use ($tagCondition, $includeNone): void {
|
||||
$builder->whereNot($tagCondition);
|
||||
if (! $includeNone) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import DateRangePicker from '@/packages/ui/src/Input/DateRangePicker.vue';
|
||||
import TagDropdown from '@/packages/ui/src/Tag/TagDropdown.vue';
|
||||
import { useTagsQuery } from '@/utils/useTagsQuery';
|
||||
import { useTagsStore } from '@/utils/useTags';
|
||||
import type { TagFilter } from '@/types/reporting';
|
||||
import type { TagMatchType } from '@/types/reporting';
|
||||
|
||||
type TimeEntryRoundingType = 'up' | 'down' | 'nearest';
|
||||
|
||||
@@ -23,7 +23,7 @@ const selectedProjects = defineModel<string[]>('selectedProjects', { required: t
|
||||
const selectedTasks = defineModel<string[]>('selectedTasks', { required: true });
|
||||
const selectedClients = defineModel<string[]>('selectedClients', { required: true });
|
||||
const selectedTags = defineModel<string[]>('selectedTags', { required: true });
|
||||
const tagFilter = defineModel<TagFilter>('tagFilter', { required: true });
|
||||
const tagMatchType = defineModel<TagMatchType>('tagMatchType', { required: true });
|
||||
const billable = defineModel<'true' | 'false' | null>('billable', { required: true });
|
||||
const roundingEnabled = defineModel<boolean>('roundingEnabled', { required: true });
|
||||
const roundingType = defineModel<TimeEntryRoundingType>('roundingType', { required: true });
|
||||
@@ -106,12 +106,12 @@ async function createTag(name: string) {
|
||||
type="button"
|
||||
class="w-full rounded-md px-2 py-1.5 text-left text-sm font-medium"
|
||||
:class="
|
||||
tagFilter === 'contains'
|
||||
tagMatchType === 'contains'
|
||||
? 'bg-card-background-active text-text-primary'
|
||||
: 'text-text-secondary hover:bg-card-background-active'
|
||||
"
|
||||
@click="
|
||||
tagFilter = 'contains';
|
||||
tagMatchType = 'contains';
|
||||
emit('submit');
|
||||
">
|
||||
Contains
|
||||
@@ -120,12 +120,12 @@ async function createTag(name: string) {
|
||||
type="button"
|
||||
class="w-full rounded-md px-2 py-1.5 text-left text-sm font-medium"
|
||||
:class="
|
||||
tagFilter === 'not_contains'
|
||||
tagMatchType === 'not_contains'
|
||||
? 'bg-card-background-active text-text-primary'
|
||||
: 'text-text-secondary hover:bg-card-background-active'
|
||||
"
|
||||
@click="
|
||||
tagFilter = 'not_contains';
|
||||
tagMatchType = 'not_contains';
|
||||
emit('submit');
|
||||
">
|
||||
Does Not Contain
|
||||
|
||||
@@ -49,7 +49,7 @@ import type { ExportFormat } from '@/types/reporting';
|
||||
import { getRandomColorWithSeed } from '@/packages/ui/src/utils/color';
|
||||
import { useProjectsQuery } from '@/utils/useProjectsQuery';
|
||||
import { useAggregatedTimeEntriesQuery } from '@/utils/useAggregatedTimeEntriesQuery';
|
||||
import type { TagFilter } from '@/types/reporting';
|
||||
import type { TagMatchType } from '@/types/reporting';
|
||||
|
||||
type TimeEntryRoundingType = 'up' | 'down' | 'nearest';
|
||||
|
||||
@@ -68,7 +68,7 @@ const selectedProjects = ref<string[]>([]);
|
||||
const selectedMembers = ref<string[]>([]);
|
||||
const selectedTasks = ref<string[]>([]);
|
||||
const selectedClients = ref<string[]>([]);
|
||||
const tagFilter = ref<TagFilter>('contains');
|
||||
const tagMatchType = ref<TagMatchType>('contains');
|
||||
|
||||
const billable = ref<'true' | 'false' | null>(null);
|
||||
const roundingEnabled = ref<boolean>(false);
|
||||
@@ -124,7 +124,7 @@ const filterParams = computed<AggregatedTimeEntriesQueryParams>(() => {
|
||||
task_ids: selectedTasks.value.length > 0 ? selectedTasks.value : undefined,
|
||||
client_ids: selectedClients.value.length > 0 ? selectedClients.value : undefined,
|
||||
tag_ids: selectedTags.value.length > 0 ? selectedTags.value : undefined,
|
||||
tag_filter: selectedTags.value.length > 0 ? tagFilter.value : undefined,
|
||||
tag_match_type: selectedTags.value.length > 0 ? tagMatchType.value : undefined,
|
||||
billable: billable.value !== null ? billable.value : undefined,
|
||||
member_id: getCurrentRole() === 'employee' ? getCurrentMembershipId() : undefined,
|
||||
rounding_type: roundingEnabled.value ? roundingType.value : undefined,
|
||||
@@ -369,7 +369,7 @@ const tableData = computed(() => {
|
||||
v-model:selected-tasks="selectedTasks"
|
||||
v-model:selected-clients="selectedClients"
|
||||
v-model:selected-tags="selectedTags"
|
||||
v-model:tag-filter="tagFilter"
|
||||
v-model:tag-match-type="tagMatchType"
|
||||
v-model:billable="billable"
|
||||
v-model:rounding-enabled="roundingEnabled"
|
||||
v-model:rounding-type="roundingType"
|
||||
|
||||
@@ -67,7 +67,7 @@ import ReportingFilterBar from '@/Components/Common/Reporting/ReportingFilterBar
|
||||
import { useTimeEntriesReportQuery } from '@/utils/useTimeEntriesReportQuery';
|
||||
import { useTimeEntriesMutations } from '@/utils/useTimeEntriesMutations';
|
||||
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||
import type { TagFilter } from '@/types/reporting';
|
||||
import type { TagMatchType } from '@/types/reporting';
|
||||
|
||||
// TimeEntryRoundingType is now defined in ReportingRoundingControls component
|
||||
type TimeEntryRoundingType = 'up' | 'down' | 'nearest';
|
||||
@@ -85,7 +85,7 @@ const selectedProjects = ref<string[]>([]);
|
||||
const selectedMembers = ref<string[]>([]);
|
||||
const selectedTasks = ref<string[]>([]);
|
||||
const selectedClients = ref<string[]>([]);
|
||||
const tagFilter = ref<TagFilter>('contains');
|
||||
const tagMatchType = ref<TagMatchType>('contains');
|
||||
const billable = ref<'true' | 'false' | null>(null);
|
||||
const roundingEnabled = ref<boolean>(false);
|
||||
const roundingType = ref<TimeEntryRoundingType>('nearest');
|
||||
@@ -117,7 +117,7 @@ function getFilterAttributes() {
|
||||
task_ids: selectedTasks.value.length > 0 ? selectedTasks.value : undefined,
|
||||
client_ids: selectedClients.value.length > 0 ? selectedClients.value : undefined,
|
||||
tag_ids: selectedTags.value.length > 0 ? selectedTags.value : undefined,
|
||||
tag_filter: selectedTags.value.length > 0 ? tagFilter.value : undefined,
|
||||
tag_match_type: selectedTags.value.length > 0 ? tagMatchType.value : undefined,
|
||||
billable: billable.value !== null ? billable.value : undefined,
|
||||
rounding_type: roundingEnabled.value ? roundingType.value : undefined,
|
||||
rounding_minutes: roundingEnabled.value ? roundingMinutes.value : undefined,
|
||||
@@ -340,7 +340,7 @@ async function downloadExport(format: ExportFormat) {
|
||||
v-model:selected-tasks="selectedTasks"
|
||||
v-model:selected-clients="selectedClients"
|
||||
v-model:selected-tags="selectedTags"
|
||||
v-model:tag-filter="tagFilter"
|
||||
v-model:tag-match-type="tagMatchType"
|
||||
v-model:billable="billable"
|
||||
v-model:rounding-enabled="roundingEnabled"
|
||||
v-model:rounding-type="roundingType"
|
||||
|
||||
@@ -448,7 +448,7 @@ const ReportStoreRequest = z
|
||||
client_ids: z.union([z.array(z.string()), z.null()]).optional(),
|
||||
project_ids: z.union([z.array(z.string()), z.null()]).optional(),
|
||||
tag_ids: z.union([z.array(z.string()), z.null()]).optional(),
|
||||
tag_filter: z.enum(['contains', 'not_contains']).optional(),
|
||||
tag_match_type: z.enum(['contains', 'not_contains']).optional(),
|
||||
task_ids: z.union([z.array(z.string()), z.null()]).optional(),
|
||||
group: TimeEntryAggregationType,
|
||||
sub_group: TimeEntryAggregationType,
|
||||
@@ -482,7 +482,7 @@ const DetailedReportResource = z
|
||||
client_ids: z.union([z.array(z.string()), z.null()]),
|
||||
project_ids: z.union([z.array(z.string()), z.null()]),
|
||||
tag_ids: z.union([z.array(z.string()), z.null()]),
|
||||
tag_filter: z.union([z.enum(['contains', 'not_contains']), z.null()]),
|
||||
tag_match_type: z.union([z.enum(['contains', 'not_contains']), z.null()]),
|
||||
task_ids: z.union([z.array(z.string()), z.null()]),
|
||||
rounding_type: z.union([z.string(), z.null()]),
|
||||
rounding_minutes: z.union([z.number(), z.null()]),
|
||||
@@ -3787,7 +3787,7 @@ Users with the permission `time-entries:view:own` can only use this en
|
||||
schema: z.array(z.string()).min(1).optional(),
|
||||
},
|
||||
{
|
||||
name: 'tag_filter',
|
||||
name: 'tag_match_type',
|
||||
type: 'Query',
|
||||
schema: z.enum(['contains', 'not_contains']).optional(),
|
||||
},
|
||||
@@ -4173,7 +4173,7 @@ If the group parameters are all set to `null` or are all missing, the
|
||||
schema: z.array(z.string()).min(1).optional(),
|
||||
},
|
||||
{
|
||||
name: 'tag_filter',
|
||||
name: 'tag_match_type',
|
||||
type: 'Query',
|
||||
schema: z.enum(['contains', 'not_contains']).optional(),
|
||||
},
|
||||
@@ -4372,7 +4372,7 @@ If the group parameters are all set to `null` or are all missing, the
|
||||
schema: z.array(z.string()).min(1).optional(),
|
||||
},
|
||||
{
|
||||
name: 'tag_filter',
|
||||
name: 'tag_match_type',
|
||||
type: 'Query',
|
||||
schema: z.enum(['contains', 'not_contains']).optional(),
|
||||
},
|
||||
@@ -4505,7 +4505,7 @@ If the group parameters are all set to `null` or are all missing, the
|
||||
schema: z.array(z.string()).min(1).optional(),
|
||||
},
|
||||
{
|
||||
name: 'tag_filter',
|
||||
name: 'tag_match_type',
|
||||
type: 'Query',
|
||||
schema: z.enum(['contains', 'not_contains']).optional(),
|
||||
},
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export type ExportFormat = 'xlsx' | 'csv' | 'ods' | 'pdf';
|
||||
export type TagFilter = 'contains' | 'not_contains';
|
||||
export type TagMatchType = 'contains' | 'not_contains';
|
||||
|
||||
Reference in New Issue
Block a user