add format check, update prettier rules, apply rules consistently

This commit is contained in:
Gregor Vostrak
2025-07-29 18:58:31 +02:00
parent b11672732b
commit cb30487a21
323 changed files with 3728 additions and 5579 deletions

View File

@@ -2,11 +2,7 @@
import VChart, { THEME_KEY } from 'vue-echarts';
import { computed, provide, inject, shallowRef, type ComputedRef } from 'vue';
import LinearGradient from 'zrender/lib/graphic/LinearGradient';
import {
formatDate,
formatHumanReadableDuration,
formatWeek,
} from '@/packages/ui/src/utils/time';
import { formatDate, formatHumanReadableDuration, formatWeek } from '@/packages/ui/src/utils/time';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
@@ -19,14 +15,7 @@ import {
import type { AggregatedTimeEntries, Organization } from '@/packages/api/src';
import { useCssVariable } from '@/utils/useCssVariable';
use([
CanvasRenderer,
BarChart,
TitleComponent,
GridComponent,
TooltipComponent,
LegendComponent,
]);
use([CanvasRenderer, BarChart, TitleComponent, GridComponent, TooltipComponent, LegendComponent]);
provide(THEME_KEY, 'dark');
@@ -174,9 +163,7 @@ const option = computed(() => ({
class="chart"
:option="option" />
<div v-else class="chart flex flex-col items-center justify-center">
<p class="text-lg text-text-primary font-semibold">
No time entries found
</p>
<p class="text-lg text-text-primary font-semibold">No time entries found</p>
<p>Try to change the filters and time range</p>
</div>
</div>

View File

@@ -27,15 +27,11 @@ function triggerDownload(format: ExportFormat) {
<template>
<Dropdown align="end">
<template #trigger>
<SecondaryButton :icon="ArrowDownTrayIcon" :loading>
Export
</SecondaryButton>
<SecondaryButton :icon="ArrowDownTrayIcon" :loading> Export </SecondaryButton>
</template>
<template #content>
<div class="flex flex-col space-y-1 p-1.5">
<SecondaryButton
class="border-0 px-2"
@click="triggerDownload('pdf')">
<SecondaryButton class="border-0 px-2" @click="triggerDownload('pdf')">
<div class="flex items-center space-x-2">
<span> Export as PDF </span>
<LockClosedIcon
@@ -43,27 +39,20 @@ function triggerDownload(format: ExportFormat) {
class="w-3.5 text-text-tertiary"></LockClosedIcon>
</div>
</SecondaryButton>
<SecondaryButton
class="border-0 px-2"
@click="triggerDownload('xlsx')"
<SecondaryButton class="border-0 px-2" @click="triggerDownload('xlsx')"
>Export as Excel</SecondaryButton
>
<SecondaryButton
class="border-0 px-2"
@click="triggerDownload('csv')"
<SecondaryButton class="border-0 px-2" @click="triggerDownload('csv')"
>Export as CSV</SecondaryButton
>
<SecondaryButton
class="border-0 px-2"
@click="triggerDownload('ods')"
<SecondaryButton class="border-0 px-2" @click="triggerDownload('ods')"
>Export as ODS
</SecondaryButton>
</div>
</template>
</Dropdown>
<UpgradeModal v-model:show="showPremiumModal">
<strong>PDF Reports</strong> are only available in solidtime
Professional.
<strong>PDF Reports</strong> are only available in solidtime Professional.
</UpgradeModal>
</template>

View File

@@ -1,9 +1,5 @@
<script setup lang="ts">
import {
ArrowDownTrayIcon,
CheckCircleIcon,
XMarkIcon,
} from '@heroicons/vue/20/solid';
import { ArrowDownTrayIcon, CheckCircleIcon, XMarkIcon } from '@heroicons/vue/20/solid';
import { Modal, PrimaryButton } from '@/packages/ui/src';
const props = defineProps<{
exportUrl: string | null;
@@ -19,30 +15,19 @@ function downloadCurrentExport() {
</script>
<template>
<Modal
closeable
max-width="lg"
:show="showExportModal"
@close="showExportModal = false">
<Modal closeable max-width="lg" :show="showExportModal" @close="showExportModal = false">
<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">
<XMarkIcon @click="showExportModal = false"></XMarkIcon>
</button>
<div class="text-center text-text-primary py-6">
<div
class="flex items-center font-semibold text-lg justify-center space-x-2 pb-2">
<CheckCircleIcon
class="text-text-tertiary w-6"></CheckCircleIcon>
<div class="flex items-center font-semibold text-lg justify-center space-x-2 pb-2">
<CheckCircleIcon class="text-text-tertiary w-6"></CheckCircleIcon>
<span> Export Successful! </span>
</div>
<div class="text-center text-sm max-w-64 mx-auto">
<p class="pb-5">
Your export is ready, you can download it with the button
below.
</p>
<PrimaryButton
:icon="ArrowDownTrayIcon"
@click="downloadCurrentExport"
<p class="pb-5">Your export is ready, you can download it with the button below.</p>
<PrimaryButton :icon="ArrowDownTrayIcon" @click="downloadCurrentExport"
>Download</PrimaryButton
>
</div>

View File

@@ -26,18 +26,8 @@ const iconClass = computed(() => {
</script>
<template>
<Button
variant="outline"
size="sm"
:class="
twMerge(
activeClass
)
">
<component
:is="icon"
:class="iconClass"
></component>
<Button variant="outline" size="sm" :class="twMerge(activeClass)">
<component :is="icon" :class="iconClass"></component>
<span class="text-nowrap"> {{ title }} </span>
<div
v-if="count"

View File

@@ -8,12 +8,10 @@ const props = defineProps<{
groupByOptions: { value: string; label: string; icon: Component }[];
}>();
const icon = computed(() => {
return props.groupByOptions.find((option) => option.value === model.value)
?.icon;
return props.groupByOptions.find((option) => option.value === model.value)?.icon;
});
const title = computed(() => {
return props.groupByOptions.find((option) => option.value === model.value)
?.label;
return props.groupByOptions.find((option) => option.value === model.value)?.label;
});
</script>

View File

@@ -1,10 +1,5 @@
<script setup lang="ts">
import {
ChartBarIcon,
CheckCircleIcon,
TagIcon,
UserGroupIcon,
} from '@heroicons/vue/20/solid';
import { ChartBarIcon, CheckCircleIcon, TagIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
import { FolderIcon } from '@heroicons/vue/16/solid';
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
import { getOrganizationCurrencyString } from '@/utils/money';
@@ -43,11 +38,7 @@ import {
type CreateReportBodyProperties,
type Organization,
} from '@/packages/api/src';
import {
getCurrentMembershipId,
getCurrentOrganizationId,
getCurrentRole,
} from '@/utils/useUser';
import { getCurrentMembershipId, getCurrentOrganizationId, getCurrentRole } from '@/utils/useUser';
import { useTagsStore } from '@/utils/useTags';
import { useSessionStorage, useStorage } from '@vueuse/core';
import { useNotificationsStore } from '@/utils/notification';
@@ -84,8 +75,7 @@ const subGroup = useStorage<GroupingOption>('reporting-sub-group', 'task');
const reportingStore = useReportingStore();
const { aggregatedGraphTimeEntries, aggregatedTableTimeEntries } =
storeToRefs(reportingStore);
const { aggregatedGraphTimeEntries, aggregatedTableTimeEntries } = storeToRefs(reportingStore);
const { groupByOptions } = reportingStore;
@@ -103,26 +93,13 @@ function getFilterAttributes(): AggregatedTimeEntriesQueryParams {
};
params = {
...params,
member_ids:
selectedMembers.value.length > 0
? selectedMembers.value
: undefined,
project_ids:
selectedProjects.value.length > 0
? selectedProjects.value
: undefined,
task_ids:
selectedTasks.value.length > 0 ? selectedTasks.value : undefined,
client_ids:
selectedClients.value.length > 0
? selectedClients.value
: undefined,
member_ids: selectedMembers.value.length > 0 ? selectedMembers.value : undefined,
project_ids: selectedProjects.value.length > 0 ? selectedProjects.value : undefined,
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,
billable: billable.value !== null ? billable.value : undefined,
member_id:
getCurrentRole() === 'employee'
? getCurrentMembershipId()
: undefined,
member_id: getCurrentRole() === 'employee' ? getCurrentMembershipId() : undefined,
rounding_type: roundingEnabled.value ? roundingType.value : undefined,
rounding_minutes: roundingEnabled.value ? roundingMinutes.value : undefined,
};
@@ -142,9 +119,7 @@ function updateGraphReporting() {
function updateTableReporting() {
const params = getFilterAttributes();
if (group.value === subGroup.value) {
const fallbackOption = groupByOptions.find(
(el) => el.value !== group.value
);
const fallbackOption = groupByOptions.find((el) => el.value !== group.value);
if (fallbackOption?.value) {
subGroup.value = fallbackOption.value;
}
@@ -162,14 +137,8 @@ function updateReporting() {
updateTableReporting();
}
function getOptimalGroupingOption(
startDate: string,
endDate: string
): 'day' | 'week' | 'month' {
const diffInDays = getDayJsInstance()(endDate).diff(
getDayJsInstance()(startDate),
'd'
);
function getOptimalGroupingOption(startDate: string, endDate: string): 'day' | 'week' | 'month' {
const diffInDays = getDayJsInstance()(endDate).diff(getDayJsInstance()(startDate), 'd');
if (diffInDays <= 31) {
return 'day';
@@ -213,10 +182,7 @@ async function downloadExport(format: ExportFormat) {
...getFilterAttributes(),
group: group.value,
sub_group: subGroup.value,
history_group: getOptimalGroupingOption(
startDate.value,
endDate.value
),
history_group: getOptimalGroupingOption(startDate.value, endDate.value),
format: format,
},
}),
@@ -249,17 +215,12 @@ const groupedPieChartData = computed(() => {
if (
name &&
aggregatedTableTimeEntries.value?.grouped_type &&
emptyPlaceholder[
aggregatedTableTimeEntries.value?.grouped_type
] === name
emptyPlaceholder[aggregatedTableTimeEntries.value?.grouped_type] === name
) {
color = '#CCCCCC';
} else if (
aggregatedTableTimeEntries.value?.grouped_type === 'project'
) {
} else if (aggregatedTableTimeEntries.value?.grouped_type === 'project') {
color =
projects.value?.find((project) => project.id === entry.key)
?.color ?? '#CCCCCC';
projects.value?.find((project) => project.id === entry.key)?.color ?? '#CCCCCC';
}
return {
value: entry.seconds,
@@ -288,10 +249,7 @@ const tableData = computed(() => {
return {
seconds: el.seconds,
cost: el.cost,
description: getNameForReportingRowEntry(
el.key,
entry.grouped_type
),
description: getNameForReportingRowEntry(el.key, entry.grouped_type),
};
}) ?? [],
};
@@ -310,20 +268,15 @@ const tableData = computed(() => {
<ReportingTabNavbar active="reporting"></ReportingTabNavbar>
</div>
<div class="flex space-x-2">
<ReportingExportButton
:download="downloadExport"></ReportingExportButton>
<ReportSaveButton
:report-properties="reportProperties"></ReportSaveButton>
<ReportingExportButton :download="downloadExport"></ReportingExportButton>
<ReportSaveButton :report-properties="reportProperties"></ReportSaveButton>
</div>
</MainContainer>
<div class="py-2.5 w-full border-b border-default-background-separator">
<MainContainer class="sm:flex space-y-4 sm:space-y-0 justify-between">
<div
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3">
<div class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3">
<div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown
v-model="selectedMembers"
@submit="updateReporting">
<MemberMultiselectDropdown v-model="selectedMembers" @submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedMembers.length"
@@ -332,9 +285,7 @@ const tableData = computed(() => {
:icon="UserGroupIcon"></ReportingFilterBadge>
</template>
</MemberMultiselectDropdown>
<ProjectMultiselectDropdown
v-model="selectedProjects"
@submit="updateReporting">
<ProjectMultiselectDropdown v-model="selectedProjects" @submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedProjects.length"
@@ -343,9 +294,7 @@ const tableData = computed(() => {
:icon="FolderIcon"></ReportingFilterBadge>
</template>
</ProjectMultiselectDropdown>
<TaskMultiselectDropdown
v-model="selectedTasks"
@submit="updateReporting">
<TaskMultiselectDropdown v-model="selectedTasks" @submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedTasks.length"
@@ -354,9 +303,7 @@ const tableData = computed(() => {
:icon="CheckCircleIcon"></ReportingFilterBadge>
</template>
</TaskMultiselectDropdown>
<ClientMultiselectDropdown
v-model="selectedClients"
@submit="updateReporting">
<ClientMultiselectDropdown v-model="selectedClients" @submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedClients.length"
@@ -401,11 +348,7 @@ const tableData = computed(() => {
<template #trigger>
<ReportingFilterBadge
:active="billable !== null"
:title="
billable === 'false'
? 'Non Billable'
: 'Billable'
"
:title="billable === 'false' ? 'Non Billable' : 'Billable'"
:icon="BillableIcon"></ReportingFilterBadge>
</template>
</SelectDropdown>
@@ -427,37 +370,26 @@ const tableData = computed(() => {
<div class="pt-10 w-full px-3 relative">
<ReportingChart
:grouped-type="aggregatedGraphTimeEntries?.grouped_type"
:grouped-data="
aggregatedGraphTimeEntries?.grouped_data
"></ReportingChart>
:grouped-data="aggregatedGraphTimeEntries?.grouped_data"></ReportingChart>
</div>
</MainContainer>
<MainContainer>
<div class="sm:grid grid-cols-4 pt-6 items-start">
<div
class="col-span-3 bg-card-background rounded-lg border border-card-border pt-3">
<div class="col-span-3 bg-card-background rounded-lg border border-card-border pt-3">
<div
class="text-sm flex text-text-primary items-center space-x-3 font-medium px-6 border-b border-card-background-separator pb-3">
<span>Group by</span>
<ReportingGroupBySelect
v-model="group"
:group-by-options="groupByOptions"
@changed="
updateTableReporting
"></ReportingGroupBySelect>
@changed="updateTableReporting"></ReportingGroupBySelect>
<span>and</span>
<ReportingGroupBySelect
v-model="subGroup"
:group-by-options="
groupByOptions.filter((el) => el.value !== group)
"
@changed="
updateTableReporting
"></ReportingGroupBySelect>
:group-by-options="groupByOptions.filter((el) => el.value !== group)"
@changed="updateTableReporting"></ReportingGroupBySelect>
</div>
<div
class="grid items-center"
style="grid-template-columns: 1fr 100px 150px">
<div class="grid items-center" style="grid-template-columns: 1fr 100px 150px">
<div
class="contents [&>*]:border-card-background-separator [&>*]:border-b [&>*]:bg-tertiary [&>*]:pb-1.5 [&>*]:pt-1 text-text-secondary text-sm">
<div class="pl-6">Name</div>
@@ -475,13 +407,11 @@ const tableData = computed(() => {
:currency="getOrganizationCurrencyString()"
:type="aggregatedTableTimeEntries.grouped_type"
:entry="entry"></ReportingRow>
<div
class="contents [&>*]:transition text-text-tertiary [&>*]:h-[50px]">
<div class="contents [&>*]:transition text-text-tertiary [&>*]:h-[50px]">
<div class="flex items-center pl-6 font-medium">
<span>Total</span>
</div>
<div
class="justify-end flex items-center font-medium">
<div class="justify-end flex items-center font-medium">
{{
formatHumanReadableDuration(
aggregatedTableTimeEntries.seconds,
@@ -490,8 +420,7 @@ const tableData = computed(() => {
)
}}
</div>
<div
class="justify-end pr-6 flex items-center font-medium">
<div class="justify-end pr-6 flex items-center font-medium">
{{
aggregatedTableTimeEntries.cost
? formatCents(
@@ -509,16 +438,13 @@ const tableData = computed(() => {
<div
v-else
class="chart flex flex-col items-center justify-center py-12 col-span-3">
<p class="text-lg text-text-primary font-medium">
No time entries found
</p>
<p class="text-lg text-text-primary font-medium">No time entries found</p>
<p>Try to change the filters and time range</p>
</div>
</div>
</div>
<div class="px-2 lg:px-4">
<ReportingPieChart
:data="groupedPieChartData"></ReportingPieChart>
<ReportingPieChart :data="groupedPieChartData"></ReportingPieChart>
</div>
</div>
</MainContainer>

View File

@@ -14,14 +14,7 @@ import { formatHumanReadableDuration } from '@/packages/ui/src/utils/time';
import { useCssVariable } from '@/utils/useCssVariable';
import type { Organization } from '@/packages/api/src';
use([
CanvasRenderer,
PieChart,
TitleComponent,
GridComponent,
TooltipComponent,
LegendComponent,
]);
use([CanvasRenderer, PieChart, TitleComponent, GridComponent, TooltipComponent, LegendComponent]);
provide(THEME_KEY, 'dark');

View File

@@ -2,20 +2,20 @@
import { Switch } from '@/Components/ui/switch';
import { Popover, PopoverContent, PopoverTrigger } from '@/Components/ui/popover';
import { Button } from '@/Components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/Components/ui/select';
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
import {
NumberField,
NumberFieldInput,
NumberFieldContent,
NumberFieldIncrement,
NumberFieldDecrement
import {
NumberField,
NumberFieldInput,
NumberFieldContent,
NumberFieldIncrement,
NumberFieldDecrement,
} from '@/Components/ui/number-field';
import { ArrowsUpDownIcon } from '@heroicons/vue/20/solid';
import { computed, ref, watch } from 'vue';
@@ -30,7 +30,7 @@ const TimeEntryRoundingType = {
Nearest: 'nearest' as const,
} as const;
type TimeEntryRoundingType = typeof TimeEntryRoundingType[keyof typeof TimeEntryRoundingType];
type TimeEntryRoundingType = (typeof TimeEntryRoundingType)[keyof typeof TimeEntryRoundingType];
interface Props {
enabled: boolean;
@@ -79,8 +79,8 @@ const selectedInterval = ref('');
// Compute the current interval value based on props
const currentInterval = computed(() => {
const predefined = predefinedIntervals.find(interval =>
interval.value !== 'custom' && parseInt(interval.value) === props.minutes
const predefined = predefinedIntervals.find(
(interval) => interval.value !== 'custom' && parseInt(interval.value) === props.minutes
);
return predefined ? predefined.value : 'custom';
});
@@ -116,10 +116,14 @@ function handleCustomMinutesChange(value: string | number) {
}
// Watch for changes in props.minutes
watch(() => props.minutes, (newMinutes) => {
customMinutes.value = newMinutes;
initializeSelectedInterval();
}, { immediate: true });
watch(
() => props.minutes,
(newMinutes) => {
customMinutes.value = newMinutes;
initializeSelectedInterval();
},
{ immediate: true }
);
watch(currentInterval, () => {
initializeSelectedInterval();
@@ -136,7 +140,9 @@ const activeClass = computed(() => {
const iconClass = computed(() => {
return twMerge(
'w-4 h-4',
props.enabled ? 'dark:text-accent-300/80 text-accent-400/80' : 'text-muted-foreground opacity-50'
props.enabled
? 'dark:text-accent-300/80 text-accent-400/80'
: 'text-muted-foreground opacity-50'
);
});
</script>
@@ -144,10 +150,7 @@ const iconClass = computed(() => {
<template>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
size="sm"
:class="twMerge(activeClass)">
<Button variant="outline" size="sm" :class="twMerge(activeClass)">
<ArrowsUpDownIcon :class="iconClass" />
Rounding {{ enabled ? 'on' : 'off' }}
</Button>
@@ -155,7 +158,9 @@ const iconClass = computed(() => {
<PopoverContent class="w-72 p-4">
<div v-if="!isAllowedToPerformPremiumAction()" class="flex flex-col space-y-2">
<span class="font-semibold text-xs">Premium</span>
<span class="text-xs text-text-secondary flex-1">Rounding is a premium feature. Upgrade to unlock this feature.</span>
<span class="text-xs text-text-secondary flex-1"
>Rounding is a premium feature. Upgrade to unlock this feature.</span
>
<Link href="/billing">
<Button size="sm" variant="input" class="items-center space-x-1">
<CreditCardIcon class="w-3.5 h-3.5 text-text-tertiary mr-1" />
@@ -166,26 +171,31 @@ const iconClass = computed(() => {
<div v-else class="space-y-4">
<div>
<div class="flex items-center justify-between">
<InputLabel for="enable-rounding" value="Enable Rounding" />
<Switch
id="enable-rounding"
:model-value="enabled"
class="data-[state=checked]:bg-accent-500"
@update:model-value="updateEnabled" />
</div>
<div class="mb-3 pb-2 pt-1 text-xs text-muted-foreground border-b border-border-secondary text-text-tertiary">
Rounding is applied to each individual time entry, not to the accumulated total.
</div>
<InputLabel for="enable-rounding" value="Enable Rounding" />
<Switch
id="enable-rounding"
:model-value="enabled"
class="data-[state=checked]:bg-accent-500"
@update:model-value="updateEnabled" />
</div>
<div
class="mb-3 pb-2 pt-1 text-xs text-muted-foreground border-b border-border-secondary text-text-tertiary">
Rounding is applied to each individual time entry, not to the accumulated
total.
</div>
</div>
<div>
<InputLabel for="rounding-type" value="Rounding Type" class="mb-2" />
<Select
<Select
:model-value="type"
:disabled="!enabled"
@update:model-value="(value) => updateType(value as TimeEntryRoundingType)">
<SelectTrigger id="rounding-type" size="small" class="w-full" :disabled="!enabled">
<SelectTrigger
id="rounding-type"
size="small"
class="w-full"
:disabled="!enabled">
<SelectValue placeholder="Select rounding type" />
</SelectTrigger>
<SelectContent>
@@ -197,23 +207,27 @@ const iconClass = computed(() => {
</div>
<div>
<InputLabel for="minutes-interval" value="Minutes Interval" class="mb-2" />
<Select
<Select
:model-value="selectedInterval"
:disabled="!enabled"
@update:model-value="(value) => handleIntervalChange(value as string)">
<SelectTrigger id="minutes-interval" size="small" class="w-full" :disabled="!enabled">
<SelectValue placeholder="Select interval" />
<SelectTrigger
id="minutes-interval"
size="small"
class="w-full"
:disabled="!enabled">
<SelectValue placeholder="Select interval" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="interval in predefinedIntervals"
<SelectItem
v-for="interval in predefinedIntervals"
:key="interval.value"
:value="interval.value">
{{ interval.label }}
</SelectItem>
</SelectContent>
</Select>
<div v-if="showCustomInput" class="mt-2">
<NumberField
id="custom-minutes"
@@ -226,7 +240,9 @@ const iconClass = computed(() => {
@update:model-value="handleCustomMinutesChange">
<NumberFieldContent>
<NumberFieldDecrement :disabled="!enabled" />
<NumberFieldInput placeholder="Enter custom minutes" :disabled="!enabled" />
<NumberFieldInput
placeholder="Enter custom minutes"
:disabled="!enabled" />
<NumberFieldIncrement :disabled="!enabled" />
</NumberFieldContent>
</NumberField>
@@ -235,4 +251,4 @@ const iconClass = computed(() => {
</div>
</PopoverContent>
</Popover>
</template>
</template>

View File

@@ -32,10 +32,7 @@ const organization = inject<ComputedRef<Organization>>('organization');
class="contents text-text-primary [&>*]:transition [&>*]:border-card-background-separator [&>*]:border-b [&>*]:h-[50px]">
<div
:class="
twMerge(
'pl-6 font-medium flex items-center space-x-3',
props.indent ? 'pl-16' : ''
)
twMerge('pl-6 font-medium flex items-center space-x-3', props.indent ? 'pl-16' : '')
">
<GroupedItemsCountButton
v-if="entry.grouped_data && entry.grouped_data?.length > 0"
@@ -57,13 +54,17 @@ const organization = inject<ComputedRef<Organization>>('organization');
}}
</div>
<div class="justify-end pr-6 flex items-center">
{{ entry.cost ? formatCents(
entry.cost,
props.currency,
organization?.currency_format,
organization?.currency_symbol,
organization?.number_format
) : '--' }}
{{
entry.cost
? formatCents(
entry.cost,
props.currency,
organization?.currency_format,
organization?.currency_symbol,
organization?.number_format
)
: '--'
}}
</div>
</div>
<div

View File

@@ -2,8 +2,8 @@
import { router } from '@inertiajs/vue3';
import TabBar from '@/Components/Common/TabBar/TabBar.vue';
import TabBarItem from '@/Components/Common/TabBar/TabBarItem.vue';
import {canViewReport} from "@/utils/permissions";
import {computed} from "vue";
import { canViewReport } from '@/utils/permissions';
import { computed } from 'vue';
defineProps<{
active: 'reporting' | 'detailed' | 'shared';
}>();
@@ -12,17 +12,11 @@ const showSharedReports = computed(() => canViewReport());
</script>
<template>
<TabBar
:model-value="active"
>
<TabBarItem
value="reporting"
@click="router.visit(route('reporting'))"
<TabBar :model-value="active">
<TabBarItem value="reporting" @click="router.visit(route('reporting'))"
>Overview</TabBarItem
>
<TabBarItem
value="detailed"
@click="router.visit(route('reporting.detailed'))"
<TabBarItem value="detailed" @click="router.visit(route('reporting.detailed'))"
>Detailed</TabBarItem
>
<TabBarItem