add missing data to public shared reports, add premium restrictions, add pdf download

This commit is contained in:
Gregor Vostrak
2024-12-03 15:03:28 +01:00
committed by Constantin Graf
parent bcb298bd6d
commit e3f981aac2
15 changed files with 525 additions and 149 deletions

View File

@@ -37,20 +37,22 @@ const props = defineProps<{
properties: CreateReportBodyProperties;
}>();
const report = ref<CreateReportBody>({
const report = ref({
name: '',
description: '',
is_public: false,
public_until: null,
properties: {},
});
const { handleApiRequestNotifications } = useNotificationsStore();
async function submit() {
report.value.properties = { ...props.properties };
await handleApiRequestNotifications(
() => createReportMutation.mutateAsync(report.value),
() =>
createReportMutation.mutateAsync({
...report.value,
properties: { ...props.properties },
}),
'Success',
'Error',
() => {
@@ -59,7 +61,6 @@ async function submit() {
description: '',
is_public: false,
public_until: null,
properties: {},
};
show.value = false;
}

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
import { SecondaryButton } from '@/packages/ui/src';
import ReportCreateModal from '@/Components/Common/Report/ReportCreateModal.vue';
import { h, ref } from 'vue';
import type { CreateReportBodyProperties } from '@/packages/api/src';
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
import UpgradeModal from '@/Components/Common/UpgradeModal.vue';
defineProps<{
reportProperties: CreateReportBodyProperties;
}>();
const showCreateReportModal = ref(false);
const showPremiumModal = ref(false);
const SaveIcon = h('div', {
innerHTML:
'<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7M7 3v4a1 1 0 0 0 1 1h7"/></g></svg>',
});
function onSaveReportClick() {
if (isAllowedToPerformPremiumAction()) {
showCreateReportModal.value = true;
} else {
showPremiumModal.value = true;
}
}
</script>
<template>
<ReportCreateModal
:properties="reportProperties"
v-model:show="showCreateReportModal"></ReportCreateModal>
<UpgradeModal v-model:show="showPremiumModal">
<strong>Sharable Reports</strong> is only available in solidtime
Professional.
</UpgradeModal>
<SecondaryButton :icon="SaveIcon" @click="onSaveReportClick"
>Save Report</SecondaryButton
>
</template>
<style scoped></style>

View File

@@ -1,15 +1,22 @@
<script setup lang="ts">
import { SecondaryButton } from '@/packages/ui/src';
import { ArrowDownTrayIcon } from '@heroicons/vue/20/solid';
import { ArrowDownTrayIcon, LockClosedIcon } from '@heroicons/vue/20/solid';
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
import type { ExportFormat } from '@/types/reporting';
import { ref } from 'vue';
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
import UpgradeModal from '@/Components/Common/UpgradeModal.vue';
const props = defineProps<{
download: (format: ExportFormat) => Promise<void>;
}>();
const loading = ref(false);
const showPremiumModal = ref(false);
function triggerDownload(format: ExportFormat) {
if (format === 'pdf' && !isAllowedToPerformPremiumAction()) {
showPremiumModal.value = true;
return;
}
loading.value = true;
props.download(format).finally(() => {
loading.value = false;
@@ -27,11 +34,15 @@ function triggerDownload(format: ExportFormat) {
<template #content>
<div class="flex flex-col space-y-1 p-1.5">
<SecondaryButton
v-if="false"
class="border-0 px-2"
@click="triggerDownload('pdf')"
>Export as PDF</SecondaryButton
>
@click="triggerDownload('pdf')">
<div class="flex items-center space-x-2">
<span> Export as PDF </span>
<LockClosedIcon
v-if="!isAllowedToPerformPremiumAction()"
class="w-3.5 text-text-tertiary"></LockClosedIcon>
</div>
</SecondaryButton>
<SecondaryButton
class="border-0 px-2"
@click="triggerDownload('xlsx')"
@@ -50,6 +61,10 @@ function triggerDownload(format: ExportFormat) {
</div>
</template>
</Dropdown>
<UpgradeModal v-model:show="showPremiumModal">
<strong>PDF Reports</strong> are only available in solidtime
Professional.
</UpgradeModal>
</template>
<style scoped></style>

View File

@@ -11,11 +11,6 @@ import {
TooltipComponent,
} from 'echarts/components';
import { formatHumanReadableDuration } from '@/packages/ui/src/utils/time';
import { getRandomColorWithSeed } from '@/packages/ui/src/utils/color';
import type { GroupedDataEntries } from '@/packages/api/src';
import { useReportingStore } from '@/utils/useReporting';
import { useProjectsStore } from '@/utils/useProjects';
import { storeToRefs } from 'pinia';
use([
CanvasRenderer,
@@ -28,36 +23,18 @@ use([
provide(THEME_KEY, 'dark');
const props = defineProps<{
data: GroupedDataEntries | null;
type: string | null;
}>();
const { getNameForReportingRowEntry, emptyPlaceholder } = useReportingStore();
const { projects } = storeToRefs(useProjectsStore());
type ReportingChartDataEntry = {
value: number;
name: string;
color: string;
}[];
const groupChartData = computed(() => {
return (
props?.data?.map((entry) => {
const name = getNameForReportingRowEntry(entry.key, props.type);
let color = getRandomColorWithSeed(entry.key ?? 'none');
if (name && props.type && emptyPlaceholder[props.type] === name) {
color = '#CCCCCC';
} else if (props.type === 'project') {
color =
projects.value?.find((project) => project.id === entry.key)
?.color ?? '#CCCCCC';
}
return {
value: entry.seconds,
name: getNameForReportingRowEntry(entry.key, props.type),
color: color,
};
}) ?? []
);
});
const props = defineProps<{
data: ReportingChartDataEntry | null;
}>();
const seriesData = computed(() => {
return groupChartData.value.map((el) => {
return props.data?.map((el) => {
return {
...el,
...{

View File

@@ -4,30 +4,23 @@ import { formatCents } from '@/packages/ui/src/utils/money';
import GroupedItemsCountButton from '@/packages/ui/src/GroupedItemsCountButton.vue';
import { ref } from 'vue';
import { twMerge } from 'tailwind-merge';
import { useReportingStore } from '@/utils/useReporting';
import { getOrganizationCurrencyString } from '@/utils/money';
const { getNameForReportingRowEntry } = useReportingStore();
type AggregatedGroupedData = GroupedData & {
grouped_type?: string | null;
grouped_data?: GroupedData[] | null;
};
type GroupedData = {
key: string | null;
seconds: number;
cost: number;
description: string | null | undefined;
};
const props = defineProps<{
entry: AggregatedGroupedData;
indent?: boolean;
type: string | null;
}>();
function getNameForKey(key: string | null) {
return getNameForReportingRowEntry(key, props.type);
}
const expanded = ref(false);
</script>
@@ -48,7 +41,7 @@ const expanded = ref(false);
{{ entry.grouped_data?.length }}
</GroupedItemsCountButton>
<span>
{{ getNameForKey(entry.key) }}
{{ entry.description }}
</span>
</div>
<div class="justify-end flex items-center">
@@ -65,8 +58,7 @@ const expanded = ref(false);
<ReportingRow
indent
v-for="subEntry in entry.grouped_data"
:type="entry?.grouped_type ?? null"
:key="subEntry.key ?? 'none'"
:key="subEntry.description ?? 'none'"
:entry="subEntry"></ReportingRow>
</div>
</template>

View File

@@ -6,7 +6,10 @@ const showUpgradeModal = ref(false);
</script>
<template>
<UpgradeModal v-model:show="showUpgradeModal"></UpgradeModal>
<UpgradeModal v-model:show="showUpgradeModal">
<strong>Project and Task Estimates</strong> is only available in
solidtime Professional.
</UpgradeModal>
<button
@click.prevent="showUpgradeModal = true"
class="inline-flex bg-secondary hover:bg-tertiary px-2 py-1 rounded border border-border-secondary hover:border-border-tertiary items-center space-x-1">

View File

@@ -2,10 +2,7 @@
import DialogModal from '@/packages/ui/src/DialogModal.vue';
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
import { Link } from '@inertiajs/vue3';
import {
isAllowedToPerformPremiumAction,
isBillingActivated,
} from '@/utils/billing';
import { isBillingActivated } from '@/utils/billing';
import { CreditCardIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
import { canManageBilling, canUpdateOrganization } from '@/utils/permissions';
import { SecondaryButton } from '@/packages/ui/src';
@@ -22,15 +19,14 @@ const show = defineModel('show', { default: false });
</template>
<template #content>
<div v-if="!isAllowedToPerformPremiumAction()">
<div>
<div
class="rounded-full flex items-center justify-center w-20 h-20 mx-auto border border-border-tertiary bg-secondary">
<UserGroupIcon class="w-12"></UserGroupIcon>
</div>
<div class="max-w-sm text-center mx-auto py-4 text-base">
<p class="py-1">
<strong>Project and Task Estimates</strong> is only
available in solidtime Professional.
<slot></slot>
</p>
<p class="py-1">
If you want to use this feature,