@@ -16,7 +16,6 @@ import {
1616import { formatCents } from ' @/packages/ui/src/utils/money' ;
1717import ReportingTabNavbar from ' @/Components/Common/Reporting/ReportingTabNavbar.vue' ;
1818import ReportingExportButton from ' @/Components/Common/Reporting/ReportingExportButton.vue' ;
19- import ReportingRoundingControls from ' @/Components/Common/Reporting/ReportingRoundingControls.vue' ;
2019import TaskMultiselectDropdown from ' @/Components/Common/Task/TaskMultiselectDropdown.vue' ;
2120import ClientMultiselectDropdown from ' @/Components/Common/Client/ClientMultiselectDropdown.vue' ;
2221import ReportingRow from ' @/Components/Common/Reporting/ReportingRow.vue' ;
@@ -32,8 +31,9 @@ import DateRangePicker from '@/packages/ui/src/Input/DateRangePicker.vue';
3231import ReportingExportModal from ' @/Components/Common/Reporting/ReportingExportModal.vue' ;
3332import ReportSaveButton from ' @/Components/Common/Report/ReportSaveButton.vue' ;
3433import TagDropdown from ' @/packages/ui/src/Tag/TagDropdown.vue' ;
34+ import ReportingPieChart from ' @/Components/Common/Reporting/ReportingPieChart.vue' ;
3535
36- import { computed , type ComputedRef , inject , onMounted , ref , watch } from ' vue' ;
36+ import { computed , type ComputedRef , inject , onMounted , ref } from ' vue' ;
3737import { type GroupingOption , useReportingStore } from ' @/utils/useReporting' ;
3838import { storeToRefs } from ' pinia' ;
3939import {
@@ -54,9 +54,6 @@ import type { ExportFormat } from '@/types/reporting';
5454import { getRandomColorWithSeed } from ' @/packages/ui/src/utils/color' ;
5555import { useProjectsStore } from ' @/utils/useProjects' ;
5656
57- // TimeEntryRoundingType is now defined in ReportingRoundingControls component
58- type TimeEntryRoundingType = ' up' | ' down' | ' nearest' ;
59-
6057const { handleApiRequestNotifications } = useNotificationsStore ();
6158
6259const startDate = useSessionStorage <string >(
@@ -74,9 +71,6 @@ const selectedTasks = ref<string[]>([]);
7471const selectedClients = ref <string []>([]);
7572
7673const billable = ref <' true' | ' false' | null >(null );
77- const roundingEnabled = ref <boolean >(false );
78- const roundingType = ref <TimeEntryRoundingType >(' nearest' );
79- const roundingMinutes = ref <number >(15 );
8074
8175const group = useStorage <GroupingOption >(' reporting-group' , ' project' );
8276const subGroup = useStorage <GroupingOption >(' reporting-sub-group' , ' task' );
@@ -90,11 +84,6 @@ const { groupByOptions } = reportingStore;
9084
9185const organization = inject <ComputedRef <Organization >>(' organization' );
9286
93- // Watch rounding enabled state to trigger updates
94- watch (roundingEnabled , () => {
95- updateReporting ();
96- });
97-
9887function getFilterAttributes(): AggregatedTimeEntriesQueryParams {
9988 let params: AggregatedTimeEntriesQueryParams = {
10089 start: getLocalizedDayJs (startDate .value ).startOf (' day' ).utc ().format (),
@@ -122,8 +111,6 @@ function getFilterAttributes(): AggregatedTimeEntriesQueryParams {
122111 getCurrentRole () === ' employee'
123112 ? getCurrentMembershipId ()
124113 : undefined ,
125- rounding_type: roundingEnabled .value ? roundingType .value : undefined ,
126- rounding_minutes: roundingEnabled .value ? roundingMinutes .value : undefined ,
127114 };
128115 return params ;
129116}
@@ -237,6 +224,42 @@ const { projects } = storeToRefs(projectsStore);
237224const showExportModal = ref (false );
238225const exportUrl = ref <string | null >(null );
239226
227+ const groupedPieChartData = computed (() => {
228+ return (
229+ aggregatedTableTimeEntries .value ?.grouped_data ?.map ((entry ) => {
230+ const name = getNameForReportingRowEntry (
231+ entry .key ,
232+ aggregatedTableTimeEntries .value ?.grouped_type
233+ );
234+ let color = getRandomColorWithSeed (entry .key ?? ' none' );
235+ if (
236+ name &&
237+ aggregatedTableTimeEntries .value ?.grouped_type &&
238+ emptyPlaceholder [
239+ aggregatedTableTimeEntries .value ?.grouped_type
240+ ] === name
241+ ) {
242+ color = ' #CCCCCC' ;
243+ } else if (
244+ aggregatedTableTimeEntries .value ?.grouped_type === ' project'
245+ ) {
246+ color =
247+ projects .value ?.find ((project ) => project .id === entry .key )
248+ ?.color ?? ' #CCCCCC' ;
249+ }
250+ return {
251+ value: entry .seconds ,
252+ name:
253+ getNameForReportingRowEntry (
254+ entry .key ,
255+ aggregatedTableTimeEntries .value ?.grouped_type
256+ ) ?? ' ' ,
257+ color: color ,
258+ };
259+ }) ?? []
260+ );
261+ });
262+
240263const tableData = computed (() => {
241264 return aggregatedTableTimeEntries .value ?.grouped_data ?.map ((entry ) => {
242265 return {
@@ -260,13 +283,6 @@ const tableData = computed(() => {
260283 };
261284 });
262285});
263-
264- const showBillableRate = computed (() => {
265- return !! (
266- getCurrentRole () !== ' employee' ||
267- organization ?.value ?.employees_can_see_billable_rates
268- );
269- });
270286 </script >
271287
272288<template >
@@ -289,7 +305,7 @@ const showBillableRate = computed(() => {
289305 <div class =" py-2.5 w-full border-b border-default-background-separator" >
290306 <MainContainer class =" sm:flex space-y-4 sm:space-y-0 justify-between" >
291307 <div
292- class =" flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3 " >
308+ class =" flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4 " >
293309 <div class =" text-sm font-medium" >Filters</div >
294310 <MemberMultiselectDropdown
295311 v-model =" selectedMembers"
@@ -379,11 +395,6 @@ const showBillableRate = computed(() => {
379395 :icon =" BillableIcon" ></ReportingFilterBadge >
380396 </template >
381397 </SelectDropdown >
382- <ReportingRoundingControls
383- v-model:enabled =" roundingEnabled"
384- v-model:type =" roundingType"
385- v-model:minutes =" roundingMinutes"
386- @change =" updateReporting" ></ReportingRoundingControls >
387398 </div >
388399 <div >
389400 <DateRangePicker
@@ -403,9 +414,9 @@ const showBillableRate = computed(() => {
403414 </div >
404415 </MainContainer >
405416 <MainContainer >
406- <div class =" pt-6 items-start" >
417+ <div class =" sm:grid grid-cols-4 pt-6 items-start" >
407418 <div
408- class =" bg-card-background rounded-lg border border-card-border pt-3" >
419+ class =" col-span-3 bg-card-background rounded-lg border border-card-border pt-3" >
409420 <div
410421 class =" text-sm flex text-text-primary items-center space-x-3 font-medium px-6 border-b border-card-background-separator pb-3" >
411422 <span >Group by</span >
@@ -425,24 +436,33 @@ const showBillableRate = computed(() => {
425436 updateTableReporting
426437 " ></ReportingGroupBySelect >
427438 </div >
428- <div class =" px-6 pt-6 pb-3" >
429- <template
430- v-for =" reportingRowEntry in tableData "
431- :key =" reportingRowEntry .description " >
432- <ReportingRow
433- :entry =" reportingRowEntry"
434- :currency =" getOrganizationCurrencyString()" ></ReportingRow >
435- </template >
439+ <div
440+ class =" grid items-center"
441+ style =" grid-template-columns : 1fr 100px 150px " >
436442 <div
443+ class =" contents [& >*]:border-card-background-separator [& >*]:border-b [& >*]:bg-tertiary [& >*]:pb-1.5 [& >*]:pt-1 text-text-secondary text-sm" >
444+ <div class =" pl-6" >Name</div >
445+ <div class =" text-right" >Duration</div >
446+ <div class =" text-right pr-6" >Cost</div >
447+ </div >
448+ <template
437449 v-if ="
438- aggregatedTableTimeEntries &&
439- aggregatedTableTimeEntries.grouped_data &&
440- aggregatedTableTimeEntries.grouped_data.length > 0
441- "
442- class =" border-t border-background-separator pt-3 mt-6 text-sm space-y-2" >
443- <div class =" justify-between items-center flex" >
444- <div class =" font-medium" >Total</div >
445- <div class =" font-medium" >
450+ aggregatedTableTimeEntries ?.grouped_data &&
451+ aggregatedTableTimeEntries .grouped_data ?.length > 0
452+ " >
453+ <ReportingRow
454+ v-for =" entry in tableData"
455+ :key =" entry.description ?? 'none'"
456+ :currency =" getOrganizationCurrencyString()"
457+ :type =" aggregatedTableTimeEntries.grouped_type"
458+ :entry =" entry" ></ReportingRow >
459+ <div
460+ class =" contents [& >*]:transition text-text-tertiary [& >*]:h-[50px]" >
461+ <div class =" flex items-center pl-6 font-medium" >
462+ <span >Total</span >
463+ </div >
464+ <div
465+ class =" justify-end flex items-center font-medium" >
446466 {{
447467 formatHumanReadableDuration(
448468 aggregatedTableTimeEntries.seconds,
@@ -451,37 +471,36 @@ const showBillableRate = computed(() => {
451471 )
452472 }}
453473 </div >
454- </div >
455- <div
456- v-if ="
457- aggregatedTableTimeEntries.cost !== null &&
458- showBillableRate
459- "
460- class =" justify-between items-center flex" >
461- <div class =" font-medium" >Total Billable</div >
462- <div class =" justify-end pr-6 flex items-center font-medium" >
474+ <div
475+ class =" justify-end pr-6 flex items-center font-medium" >
463476 {{
464- formatCents(
465- aggregatedTableTimeEntries.cost,
466- getOrganizationCurrencyString(),
467- organization?.currency_format,
468- organization?.currency_symbol,
469- organization?.number_format
470- )
477+ aggregatedTableTimeEntries.cost
478+ ? formatCents(
479+ aggregatedTableTimeEntries.cost,
480+ getOrganizationCurrencyString(),
481+ organization?.currency_format,
482+ organization?.currency_symbol,
483+ organization?.number_format
484+ )
485+ : '--'
471486 }}
472487 </div >
473488 </div >
474- </div >
489+ </template >
475490 <div
476491 v-else
477- class =" chart flex flex-col items-center justify-center py-12" >
478- <p class =" text-lg text-text-primary font-medium " >
492+ class =" chart flex flex-col items-center justify-center py-12 col-span-3 " >
493+ <p class =" text-lg text-text-primary font-semibold " >
479494 No time entries found
480495 </p >
481496 <p >Try to change the filters and time range</p >
482497 </div >
483498 </div >
484499 </div >
500+ <div class =" px-2 lg:px-4" >
501+ <ReportingPieChart
502+ :data =" groupedPieChartData" ></ReportingPieChart >
503+ </div >
485504 </div >
486505 </MainContainer >
487506</template >
0 commit comments