mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-18 05:02:14 +01:00
restrict group and subgroup so they are always different
This commit is contained in:
@@ -1,50 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FolderIcon } from '@heroicons/vue/16/solid';
|
|
||||||
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
import SelectDropdown from '@/Components/Common/SelectDropdown.vue';
|
||||||
import Badge from '@/Components/Common/Badge.vue';
|
import Badge from '@/Components/Common/Badge.vue';
|
||||||
import { computed } from 'vue';
|
import { type Component, computed } from 'vue';
|
||||||
import {
|
|
||||||
CheckCircleIcon,
|
|
||||||
UserCircleIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
} from '@heroicons/vue/20/solid';
|
|
||||||
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
|
||||||
|
|
||||||
const groupByOptions = [
|
|
||||||
{
|
|
||||||
label: 'Members',
|
|
||||||
value: 'user',
|
|
||||||
icon: UserGroupIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Projects',
|
|
||||||
value: 'project',
|
|
||||||
icon: FolderIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Tasks',
|
|
||||||
value: 'task',
|
|
||||||
icon: CheckCircleIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Clients',
|
|
||||||
value: 'client',
|
|
||||||
icon: UserCircleIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Billable',
|
|
||||||
value: 'billable',
|
|
||||||
icon: BillableIcon,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const model = defineModel<string | null>({ default: null });
|
const model = defineModel<string | null>({ default: null });
|
||||||
|
const props = defineProps<{
|
||||||
|
groupByOptions: { value: string; label: string; icon: Component }[];
|
||||||
|
}>();
|
||||||
const icon = computed(() => {
|
const icon = computed(() => {
|
||||||
return groupByOptions.find((option) => option.value === model.value)?.icon;
|
return props.groupByOptions.find((option) => option.value === model.value)
|
||||||
|
?.icon;
|
||||||
});
|
});
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
return groupByOptions.find((option) => option.value === model.value)?.label;
|
return props.groupByOptions.find((option) => option.value === model.value)
|
||||||
|
?.label;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import ReportingChart from '@/Components/Common/Reporting/ReportingChart.vue';
|
|||||||
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { formatHumanReadableDuration, getDayJsInstance } from '@/utils/time';
|
import { formatHumanReadableDuration, getDayJsInstance } from '@/utils/time';
|
||||||
import { useReportingStore } from '@/utils/useReporting';
|
import { type GroupingOption, useReportingStore } from '@/utils/useReporting';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import TagDropdown from '@/Components/Common/Tag/TagDropdown.vue';
|
import TagDropdown from '@/Components/Common/Tag/TagDropdown.vue';
|
||||||
import type { AggregatedTimeEntriesQueryParams } from '@/utils/api';
|
import type { AggregatedTimeEntriesQueryParams } from '@/utils/api';
|
||||||
@@ -42,11 +42,16 @@ const selectedClients = ref<string[]>([]);
|
|||||||
|
|
||||||
const billable = ref<'true' | 'false' | null>(null);
|
const billable = ref<'true' | 'false' | null>(null);
|
||||||
|
|
||||||
type GroupingOption = 'project' | 'task' | 'user' | 'billable' | 'client';
|
|
||||||
|
|
||||||
const group = ref<GroupingOption>('project');
|
const group = ref<GroupingOption>('project');
|
||||||
const subGroup = ref<GroupingOption>('task');
|
const subGroup = ref<GroupingOption>('task');
|
||||||
|
|
||||||
|
const reportingStore = useReportingStore();
|
||||||
|
|
||||||
|
const { aggregatedGraphTimeEntries, aggregatedTableTimeEntries } =
|
||||||
|
storeToRefs(reportingStore);
|
||||||
|
|
||||||
|
const { groupByOptions } = reportingStore;
|
||||||
|
|
||||||
function getFilterAttributes() {
|
function getFilterAttributes() {
|
||||||
let params: AggregatedTimeEntriesQueryParams = {
|
let params: AggregatedTimeEntriesQueryParams = {
|
||||||
start: getDayJsInstance()(startDate.value).utc().format(),
|
start: getDayJsInstance()(startDate.value).utc().format(),
|
||||||
@@ -90,6 +95,14 @@ function updateGraphReporting() {
|
|||||||
|
|
||||||
function updateTableReporting() {
|
function updateTableReporting() {
|
||||||
const params = getFilterAttributes();
|
const params = getFilterAttributes();
|
||||||
|
if (group.value === subGroup.value) {
|
||||||
|
const fallbackOption = groupByOptions.find(
|
||||||
|
(el) => el.value !== group.value
|
||||||
|
);
|
||||||
|
if (fallbackOption?.value) {
|
||||||
|
subGroup.value = fallbackOption.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (getCurrentRole() === 'employee') {
|
if (getCurrentRole() === 'employee') {
|
||||||
params.member_id = getCurrentMembershipId();
|
params.member_id = getCurrentMembershipId();
|
||||||
}
|
}
|
||||||
@@ -103,11 +116,6 @@ function updateReporting() {
|
|||||||
updateTableReporting();
|
updateTableReporting();
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportingStore = useReportingStore();
|
|
||||||
|
|
||||||
const { aggregatedGraphTimeEntries, aggregatedTableTimeEntries } =
|
|
||||||
storeToRefs(reportingStore);
|
|
||||||
|
|
||||||
function getOptimalGroupingOption(diff: number): 'day' | 'week' | 'month' {
|
function getOptimalGroupingOption(diff: number): 'day' | 'week' | 'month' {
|
||||||
if (diff <= 31) {
|
if (diff <= 31) {
|
||||||
return 'day';
|
return 'day';
|
||||||
@@ -248,10 +256,16 @@ onMounted(() => {
|
|||||||
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
|
||||||
|
:group-by-options="groupByOptions"
|
||||||
@changed="updateTableReporting"
|
@changed="updateTableReporting"
|
||||||
v-model="group"></ReportingGroupBySelect>
|
v-model="group"></ReportingGroupBySelect>
|
||||||
<span>and</span>
|
<span>and</span>
|
||||||
<ReportingGroupBySelect
|
<ReportingGroupBySelect
|
||||||
|
:group-by-options="
|
||||||
|
groupByOptions.filter(
|
||||||
|
(el) => el.value !== group
|
||||||
|
)
|
||||||
|
"
|
||||||
@changed="updateTableReporting"
|
@changed="updateTableReporting"
|
||||||
v-model="subGroup"></ReportingGroupBySelect>
|
v-model="subGroup"></ReportingGroupBySelect>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore, storeToRefs } from 'pinia';
|
import { defineStore, storeToRefs } from 'pinia';
|
||||||
import { api } from '../../../openapi.json.client';
|
import { api } from '../../../openapi.json.client';
|
||||||
import { computed, ref } from 'vue';
|
import { type Component, computed, ref } from 'vue';
|
||||||
import type {
|
import type {
|
||||||
AggregatedTimeEntries,
|
AggregatedTimeEntries,
|
||||||
AggregatedTimeEntriesQueryParams,
|
AggregatedTimeEntriesQueryParams,
|
||||||
@@ -12,6 +12,20 @@ import { useProjectsStore } from '@/utils/useProjects';
|
|||||||
import { useMembersStore } from '@/utils/useMembers';
|
import { useMembersStore } from '@/utils/useMembers';
|
||||||
import { useTasksStore } from '@/utils/useTasks';
|
import { useTasksStore } from '@/utils/useTasks';
|
||||||
import { useClientsStore } from '@/utils/useClients';
|
import { useClientsStore } from '@/utils/useClients';
|
||||||
|
import {
|
||||||
|
CheckCircleIcon,
|
||||||
|
UserCircleIcon,
|
||||||
|
UserGroupIcon,
|
||||||
|
} from '@heroicons/vue/20/solid';
|
||||||
|
import { FolderIcon } from '@heroicons/vue/16/solid';
|
||||||
|
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
||||||
|
|
||||||
|
export type GroupingOption =
|
||||||
|
| 'project'
|
||||||
|
| 'task'
|
||||||
|
| 'user'
|
||||||
|
| 'billable'
|
||||||
|
| 'client';
|
||||||
|
|
||||||
export const useReportingStore = defineStore('reporting', () => {
|
export const useReportingStore = defineStore('reporting', () => {
|
||||||
const reportingGraphResponse = ref<ReportingResponse | null>(null);
|
const reportingGraphResponse = ref<ReportingResponse | null>(null);
|
||||||
@@ -112,11 +126,44 @@ export const useReportingStore = defineStore('reporting', () => {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupByOptions: {
|
||||||
|
label: string;
|
||||||
|
value: GroupingOption;
|
||||||
|
icon: Component;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: 'Members',
|
||||||
|
value: 'user',
|
||||||
|
icon: UserGroupIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Projects',
|
||||||
|
value: 'project',
|
||||||
|
icon: FolderIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tasks',
|
||||||
|
value: 'task',
|
||||||
|
icon: CheckCircleIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Clients',
|
||||||
|
value: 'client',
|
||||||
|
icon: UserCircleIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billable',
|
||||||
|
value: 'billable',
|
||||||
|
icon: BillableIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aggregatedGraphTimeEntries,
|
aggregatedGraphTimeEntries,
|
||||||
fetchGraphReporting,
|
fetchGraphReporting,
|
||||||
fetchTableReporting,
|
fetchTableReporting,
|
||||||
aggregatedTableTimeEntries,
|
aggregatedTableTimeEntries,
|
||||||
getNameForReportingRowEntry,
|
getNameForReportingRowEntry,
|
||||||
|
groupByOptions,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user