mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 04:32:15 +01:00
Add Euro Symbol as Billable Icon when EUR is the organization currency.
fixes #423
This commit is contained in:
@@ -201,7 +201,7 @@ const firstProjectId = computed(() => projects.value[0]?.id ?? '');
|
|||||||
<TimeTrackerProjectTaskDropdown
|
<TimeTrackerProjectTaskDropdown
|
||||||
v-model:project="currentTimeEntry.project_id"
|
v-model:project="currentTimeEntry.project_id"
|
||||||
v-model:task="currentTimeEntry.task_id"
|
v-model:task="currentTimeEntry.task_id"
|
||||||
variant="input"
|
variant="outline"
|
||||||
:projects="projects"
|
:projects="projects"
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:clients="activeClients"
|
:clients="activeClients"
|
||||||
@@ -225,7 +225,7 @@ const firstProjectId = computed(() => projects.value[0]?.id ?? '');
|
|||||||
<TimeTrackerProjectTaskDropdown
|
<TimeTrackerProjectTaskDropdown
|
||||||
v-model:project="currentTimeEntry.project_id"
|
v-model:project="currentTimeEntry.project_id"
|
||||||
v-model:task="currentTimeEntry.task_id"
|
v-model:task="currentTimeEntry.task_id"
|
||||||
variant="input"
|
variant="outline"
|
||||||
:projects="projects"
|
:projects="projects"
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:clients="activeClients"
|
:clients="activeClients"
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ async function createTag(name: string) {
|
|||||||
|
|
||||||
<Select v-model="billable" @update:model-value="emit('submit')">
|
<Select v-model="billable" @update:model-value="emit('submit')">
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
size="small"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:active="billable !== null"
|
:active="billable !== null"
|
||||||
:show-chevron="false">
|
:show-chevron="false">
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const title = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Select v-model="model" @update:model-value="emit('changed')">
|
<Select v-model="model" @update:model-value="emit('changed')">
|
||||||
<SelectTrigger size="small" :show-chevron="false">
|
<SelectTrigger size="sm" :show-chevron="false">
|
||||||
<SelectValue class="flex items-center gap-2">
|
<SelectValue class="flex items-center gap-2">
|
||||||
<component :is="icon" class="h-4 text-icon-default" />
|
<component :is="icon" class="h-4 text-icon-default" />
|
||||||
<span>{{ title }}</span>
|
<span>{{ title }}</span>
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
|||||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||||
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
|
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { Button as UiButton } from '@/packages/ui/src';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
import { Button } from '@/Components/ui/button';
|
|
||||||
import { openFeedback } from '@/utils/feedback';
|
import { openFeedback } from '@/utils/feedback';
|
||||||
import { CommandPaletteProvider } from '@/Components/CommandPalette';
|
import { CommandPaletteProvider } from '@/Components/CommandPalette';
|
||||||
import { useCommandPalette } from '@/utils/useCommandPalette';
|
import { useCommandPalette } from '@/utils/useCommandPalette';
|
||||||
@@ -263,14 +262,14 @@ const page = usePage<{
|
|||||||
:icon="Cog6ToothIcon"
|
:icon="Cog6ToothIcon"
|
||||||
:href="route('profile.show')"></NavigationSidebarItem>
|
:href="route('profile.show')"></NavigationSidebarItem>
|
||||||
|
|
||||||
<UiButton
|
<Button
|
||||||
v-if="page.props.has_services_extension"
|
v-if="page.props.has_services_extension"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
class="rounded-full ml-2 flex h-6 w-6 items-center text-xs text-icon-default justify-center"
|
class="rounded-full ml-2 flex h-6 w-6 items-center text-xs text-icon-default justify-center"
|
||||||
@click="openFeedback">
|
@click="openFeedback">
|
||||||
?
|
?
|
||||||
</UiButton>
|
</Button>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function updateValue(client: { id: string | null; name: string }) {
|
|||||||
<UseFocusTrap v-if="open" :options="{ immediate: true, allowOutsideClick: true }">
|
<UseFocusTrap v-if="open" :options="{ immediate: true, allowOutsideClick: true }">
|
||||||
<ComboboxRoot
|
<ComboboxRoot
|
||||||
v-model:search-term="searchValue"
|
v-model:search-term="searchValue"
|
||||||
:open="open"
|
v-model:open="open"
|
||||||
:model-value="currentClient"
|
:model-value="currentClient"
|
||||||
class="relative"
|
class="relative"
|
||||||
@update:model-value="updateValue">
|
@update:model-value="updateValue">
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||||
|
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||||
|
import DollarIcon from './DollarIcon.vue';
|
||||||
|
import EuroIcon from './EuroIcon.vue';
|
||||||
|
|
||||||
|
const { organization } = useOrganizationQuery(getCurrentOrganizationId()!);
|
||||||
|
const icon = computed(() => (organization.value?.currency === 'EUR' ? EuroIcon : DollarIcon));
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<component :is="icon" />
|
||||||
<path
|
|
||||||
d="M4 1V13M1 10.182L1.879 10.841C3.05 11.72 4.949 11.72 6.121 10.841C7.293 9.962 7.293 8.538 6.121 7.659C5.536 7.219 4.768 7 4 7C3.275 7 2.55 6.78 1.997 6.341C0.891 5.462 0.891 4.038 1.997 3.159C3.103 2.28 4.897 2.28 6.003 3.159L6.418 3.489"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|||||||
10
resources/js/packages/ui/src/Icons/DollarIcon.vue
Normal file
10
resources/js/packages/ui/src/Icons/DollarIcon.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<svg viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M4 1V13M1 10.182L1.879 10.841C3.05 11.72 4.949 11.72 6.121 10.841C7.293 9.962 7.293 8.538 6.121 7.659C5.536 7.219 4.768 7 4 7C3.275 7 2.55 6.78 1.997 6.341C0.891 5.462 0.891 4.038 1.997 3.159C3.103 2.28 4.897 2.28 6.003 3.159L6.418 3.489"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
10
resources/js/packages/ui/src/Icons/EuroIcon.vue
Normal file
10
resources/js/packages/ui/src/Icons/EuroIcon.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M9.44214 2.80599C8.64364 1.84779 7.44588 1.20898 6.0485 1.20898C3.85262 1.20898 2.05599 3.36494 2.05599 6C2.05599 8.63506 3.85262 10.791 6.0485 10.791C7.44588 10.791 8.64364 10.1522 9.44214 9.19401M1.05786 4.80225H6.64738M1.05786 7.19775H6.64738"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/packages/ui/src/tooltip';
|
||||||
const active = defineModel({ default: false });
|
const active = defineModel({ default: false });
|
||||||
const emit = defineEmits(['changed']);
|
const emit = defineEmits(['changed']);
|
||||||
function toggleBillable() {
|
function toggleBillable() {
|
||||||
@@ -12,12 +18,16 @@ function toggleBillable() {
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
size?: 'small' | 'base';
|
size?: 'small' | 'base';
|
||||||
|
faded?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
size: 'base',
|
size: 'base',
|
||||||
|
faded: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tooltipLabel = computed(() => (active.value ? 'Billable' : 'Non Billable'));
|
||||||
|
|
||||||
const iconColorClasses = computed(() => {
|
const iconColorClasses = computed(() => {
|
||||||
if (active.value) {
|
if (active.value) {
|
||||||
return 'text-input-select-active focus:text-input-select-active-hover hover:text-input-select-active-hover';
|
return 'text-input-select-active focus:text-input-select-active-hover hover:text-input-select-active-hover';
|
||||||
@@ -38,17 +48,28 @@ const iconSizeWrapperClasses = props.size === 'small' ? 'w-6 sm:w-8 h-6 sm:h-8'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<TooltipProvider>
|
||||||
:class="
|
<Tooltip disable-closing-trigger>
|
||||||
twMerge(
|
<TooltipTrigger as-child>
|
||||||
iconColorClasses,
|
<button
|
||||||
iconSizeWrapperClasses,
|
:aria-label="tooltipLabel"
|
||||||
'flex-shrink-0 ring-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center'
|
:class="
|
||||||
)
|
twMerge(
|
||||||
"
|
iconColorClasses,
|
||||||
@click="toggleBillable">
|
iconSizeWrapperClasses,
|
||||||
<BillableIcon :class="iconSizeClasses"></BillableIcon>
|
'flex-shrink-0 ring-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center',
|
||||||
</button>
|
faded
|
||||||
|
? 'opacity-50 group-hover:opacity-100 focus-visible:opacity-100'
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
"
|
||||||
|
@click="toggleBillable">
|
||||||
|
<BillableIcon :class="iconSizeClasses"></BillableIcon>
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{{ tooltipLabel }}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from '@/packages/ui/src/utils/time';
|
} from '@/packages/ui/src/utils/time';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/packages/ui/src/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/packages/ui/src/popover';
|
||||||
import { Calendar } from '@/Components/ui/calendar';
|
import { Calendar } from '@/Components/ui/calendar';
|
||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
import { CalendarIcon } from 'lucide-vue-next';
|
import { CalendarIcon } from 'lucide-vue-next';
|
||||||
import { parseDate, type DateValue } from '@internationalized/date';
|
import { parseDate, type DateValue } from '@internationalized/date';
|
||||||
import type { Organization } from '@/packages/api/src';
|
import type { Organization } from '@/packages/api/src';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
|
|||||||
import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time';
|
import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import TimePickerSimple from '@/packages/ui/src/Input/TimePickerSimple.vue';
|
import TimePickerSimple from '@/packages/ui/src/Input/TimePickerSimple.vue';
|
||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
start: string;
|
start: string;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { computed, ref, watch } from 'vue';
|
|||||||
import TagCreateModal from '@/packages/ui/src/Tag/TagCreateModal.vue';
|
import TagCreateModal from '@/packages/ui/src/Tag/TagCreateModal.vue';
|
||||||
import Checkbox from '@/packages/ui/src/Input/Checkbox.vue';
|
import Checkbox from '@/packages/ui/src/Input/Checkbox.vue';
|
||||||
import type { Tag } from '@/packages/api/src';
|
import type { Tag } from '@/packages/api/src';
|
||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
import {
|
import {
|
||||||
ComboboxAnchor,
|
ComboboxAnchor,
|
||||||
ComboboxContent,
|
ComboboxContent,
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ function onSelectChange(checked: boolean) {
|
|||||||
<BillableToggleButton
|
<BillableToggleButton
|
||||||
:model-value="timeEntry.billable"
|
:model-value="timeEntry.billable"
|
||||||
size="small"
|
size="small"
|
||||||
class="opacity-50 focus-visible:opacity-100 group-hover:opacity-100"
|
faded
|
||||||
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import BillableToggleButton from '@/packages/ui/src/Input/BillableToggleButton.v
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
||||||
import { Checkbox } from '@/packages/ui/src';
|
import { Checkbox } from '@/packages/ui/src';
|
||||||
import { twMerge } from 'tailwind-merge';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
timeEntry: TimeEntry;
|
timeEntry: TimeEntry;
|
||||||
@@ -150,9 +149,7 @@ async function handleDeleteTimeEntry() {
|
|||||||
<BillableToggleButton
|
<BillableToggleButton
|
||||||
:model-value="timeEntry.billable"
|
:model-value="timeEntry.billable"
|
||||||
size="small"
|
size="small"
|
||||||
:class="
|
faded
|
||||||
twMerge('opacity-50 group-hover:opacity-100 focus-visible:opacity-100')
|
|
||||||
"
|
|
||||||
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<TimeEntryRangeSelector
|
<TimeEntryRangeSelector
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import type {
|
|||||||
import { PlusIcon, PlusCircleIcon, MinusIcon, XMarkIcon } from '@heroicons/vue/16/solid';
|
import { PlusIcon, PlusCircleIcon, MinusIcon, XMarkIcon } from '@heroicons/vue/16/solid';
|
||||||
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { Button } from '@/Components/ui/button';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
|
|
||||||
const task = defineModel<string | null>('task', {
|
const task = defineModel<string | null>('task', {
|
||||||
default: null,
|
default: null,
|
||||||
@@ -61,7 +61,7 @@ const props = withDefaults(
|
|||||||
class?: string;
|
class?: string;
|
||||||
variant?: 'input' | 'ghost' | 'outline';
|
variant?: 'input' | 'ghost' | 'outline';
|
||||||
align?: 'center' | 'end' | 'start';
|
align?: 'center' | 'end' | 'start';
|
||||||
size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'input';
|
size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon';
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
emptyPlaceholder: 'No Project',
|
emptyPlaceholder: 'No Project',
|
||||||
|
|||||||
@@ -76,6 +76,17 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Re-run filter when items are dynamically added/removed (e.g. entity search results)
|
||||||
|
watch(
|
||||||
|
allItems,
|
||||||
|
() => {
|
||||||
|
if (filterState.search) {
|
||||||
|
filterItems();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// Sync external searchTerm prop changes to internal state
|
// Sync external searchTerm prop changes to internal state
|
||||||
watch(
|
watch(
|
||||||
() => props.searchTerm,
|
() => props.searchTerm,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
--theme-color-input-background: var(--color-bg-secondary);
|
--theme-color-input-background: var(--color-bg-secondary);
|
||||||
|
|
||||||
--theme-color-input-select-active: rgb(var(--color-accent-300));
|
--theme-color-input-select-active: rgb(var(--color-accent-300));
|
||||||
--theme-color-input-select-active-hover: rgb(var(--color-accent-200));
|
--theme-color-input-select-active-hover: rgb(var(--color-accent-400));
|
||||||
|
|
||||||
--color-accent-default: rgba(var(--color-accent-300), 0.2);
|
--color-accent-default: rgba(var(--color-accent-300), 0.2);
|
||||||
--color-accent-foreground: rgb(var(--color-accent-100));
|
--color-accent-foreground: rgb(var(--color-accent-100));
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import {
|
|||||||
SunIcon,
|
SunIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
ComputerDesktopIcon,
|
ComputerDesktopIcon,
|
||||||
CurrencyDollarIcon,
|
|
||||||
ClipboardDocumentListIcon,
|
ClipboardDocumentListIcon,
|
||||||
BuildingOfficeIcon,
|
BuildingOfficeIcon,
|
||||||
} from '@heroicons/vue/20/solid';
|
} from '@heroicons/vue/20/solid';
|
||||||
|
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||||
import type { Organization } from '@/types/models';
|
import type { Organization } from '@/types/models';
|
||||||
|
|
||||||
export type CommandGroup =
|
export type CommandGroup =
|
||||||
@@ -340,7 +340,7 @@ export function createActiveTimerCommands(
|
|||||||
{
|
{
|
||||||
id: 'timer-toggle-billable',
|
id: 'timer-toggle-billable',
|
||||||
label: 'Toggle Billable',
|
label: 'Toggle Billable',
|
||||||
icon: CurrencyDollarIcon,
|
icon: BillableIcon,
|
||||||
keywords: ['billable', 'non-billable', 'money'],
|
keywords: ['billable', 'non-billable', 'money'],
|
||||||
group: 'active-timer',
|
group: 'active-timer',
|
||||||
action: activeTimerActions.toggleBillable,
|
action: activeTimerActions.toggleBillable,
|
||||||
|
|||||||
Reference in New Issue
Block a user