mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 11:12:16 +01:00
add missing data to public shared reports, add premium restrictions, add pdf download
This commit is contained in:
committed by
Constantin Graf
parent
bcb298bd6d
commit
e3f981aac2
@@ -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;
|
||||
}
|
||||
|
||||
41
resources/js/Components/Common/Report/ReportSaveButton.vue
Normal file
41
resources/js/Components/Common/Report/ReportSaveButton.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
...{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user