mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02: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
|
||||
v-model:project="currentTimeEntry.project_id"
|
||||
v-model:task="currentTimeEntry.task_id"
|
||||
variant="input"
|
||||
variant="outline"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:clients="activeClients"
|
||||
@@ -225,7 +225,7 @@ const firstProjectId = computed(() => projects.value[0]?.id ?? '');
|
||||
<TimeTrackerProjectTaskDropdown
|
||||
v-model:project="currentTimeEntry.project_id"
|
||||
v-model:task="currentTimeEntry.task_id"
|
||||
variant="input"
|
||||
variant="outline"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:clients="activeClients"
|
||||
|
||||
@@ -103,7 +103,7 @@ async function createTag(name: string) {
|
||||
|
||||
<Select v-model="billable" @update:model-value="emit('submit')">
|
||||
<SelectTrigger
|
||||
size="small"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
:active="billable !== null"
|
||||
:show-chevron="false">
|
||||
|
||||
@@ -25,7 +25,7 @@ const title = computed(() => {
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<component :is="icon" class="h-4 text-icon-default" />
|
||||
<span>{{ title }}</span>
|
||||
|
||||
@@ -48,8 +48,7 @@ import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Button as UiButton } from '@/packages/ui/src';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
import { openFeedback } from '@/utils/feedback';
|
||||
import { CommandPaletteProvider } from '@/Components/CommandPalette';
|
||||
import { useCommandPalette } from '@/utils/useCommandPalette';
|
||||
@@ -263,14 +262,14 @@ const page = usePage<{
|
||||
:icon="Cog6ToothIcon"
|
||||
:href="route('profile.show')"></NavigationSidebarItem>
|
||||
|
||||
<UiButton
|
||||
<Button
|
||||
v-if="page.props.has_services_extension"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
class="rounded-full ml-2 flex h-6 w-6 items-center text-xs text-icon-default justify-center"
|
||||
@click="openFeedback">
|
||||
?
|
||||
</UiButton>
|
||||
</Button>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,7 @@ function updateValue(client: { id: string | null; name: string }) {
|
||||
<UseFocusTrap v-if="open" :options="{ immediate: true, allowOutsideClick: true }">
|
||||
<ComboboxRoot
|
||||
v-model:search-term="searchValue"
|
||||
:open="open"
|
||||
v-model:open="open"
|
||||
:model-value="currentClient"
|
||||
class="relative"
|
||||
@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>
|
||||
<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>
|
||||
<component :is="icon" />
|
||||
</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 { twMerge } from 'tailwind-merge';
|
||||
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 emit = defineEmits(['changed']);
|
||||
function toggleBillable() {
|
||||
@@ -12,12 +18,16 @@ function toggleBillable() {
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
size?: 'small' | 'base';
|
||||
faded?: boolean;
|
||||
}>(),
|
||||
{
|
||||
size: 'base',
|
||||
faded: false,
|
||||
}
|
||||
);
|
||||
|
||||
const tooltipLabel = computed(() => (active.value ? 'Billable' : 'Non Billable'));
|
||||
|
||||
const iconColorClasses = computed(() => {
|
||||
if (active.value) {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:class="
|
||||
twMerge(
|
||||
iconColorClasses,
|
||||
iconSizeWrapperClasses,
|
||||
'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'
|
||||
)
|
||||
"
|
||||
@click="toggleBillable">
|
||||
<BillableIcon :class="iconSizeClasses"></BillableIcon>
|
||||
</button>
|
||||
<TooltipProvider>
|
||||
<Tooltip disable-closing-trigger>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
:aria-label="tooltipLabel"
|
||||
:class="
|
||||
twMerge(
|
||||
iconColorClasses,
|
||||
iconSizeWrapperClasses,
|
||||
'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',
|
||||
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>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@/packages/ui/src/utils/time';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/packages/ui/src/popover';
|
||||
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 { parseDate, type DateValue } from '@internationalized/date';
|
||||
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 dayjs from 'dayjs';
|
||||
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<{
|
||||
start: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { computed, ref, watch } from 'vue';
|
||||
import TagCreateModal from '@/packages/ui/src/Tag/TagCreateModal.vue';
|
||||
import Checkbox from '@/packages/ui/src/Input/Checkbox.vue';
|
||||
import type { Tag } from '@/packages/api/src';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
import {
|
||||
ComboboxAnchor,
|
||||
ComboboxContent,
|
||||
|
||||
@@ -136,7 +136,7 @@ function onSelectChange(checked: boolean) {
|
||||
<BillableToggleButton
|
||||
:model-value="timeEntry.billable"
|
||||
size="small"
|
||||
class="opacity-50 focus-visible:opacity-100 group-hover:opacity-100"
|
||||
faded
|
||||
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
||||
<div class="flex-1">
|
||||
<button
|
||||
|
||||
@@ -21,7 +21,6 @@ import BillableToggleButton from '@/packages/ui/src/Input/BillableToggleButton.v
|
||||
import { computed, ref } from 'vue';
|
||||
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
||||
import { Checkbox } from '@/packages/ui/src';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
const props = defineProps<{
|
||||
timeEntry: TimeEntry;
|
||||
@@ -150,9 +149,7 @@ async function handleDeleteTimeEntry() {
|
||||
<BillableToggleButton
|
||||
:model-value="timeEntry.billable"
|
||||
size="small"
|
||||
:class="
|
||||
twMerge('opacity-50 group-hover:opacity-100 focus-visible:opacity-100')
|
||||
"
|
||||
faded
|
||||
@changed="updateTimeEntryBillable"></BillableToggleButton>
|
||||
<div class="flex-1">
|
||||
<TimeEntryRangeSelector
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
import { PlusIcon, PlusCircleIcon, MinusIcon, XMarkIcon } from '@heroicons/vue/16/solid';
|
||||
import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Button } from '@/packages/ui/src/Buttons';
|
||||
|
||||
const task = defineModel<string | null>('task', {
|
||||
default: null,
|
||||
@@ -61,7 +61,7 @@ const props = withDefaults(
|
||||
class?: string;
|
||||
variant?: 'input' | 'ghost' | 'outline';
|
||||
align?: 'center' | 'end' | 'start';
|
||||
size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'input';
|
||||
size?: 'default' | 'xs' | 'sm' | 'lg' | 'icon';
|
||||
}>(),
|
||||
{
|
||||
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
|
||||
watch(
|
||||
() => props.searchTerm,
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
--theme-color-input-background: var(--color-bg-secondary);
|
||||
|
||||
--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-foreground: rgb(var(--color-accent-100));
|
||||
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
SunIcon,
|
||||
MoonIcon,
|
||||
ComputerDesktopIcon,
|
||||
CurrencyDollarIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
BuildingOfficeIcon,
|
||||
} from '@heroicons/vue/20/solid';
|
||||
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||
import type { Organization } from '@/types/models';
|
||||
|
||||
export type CommandGroup =
|
||||
@@ -340,7 +340,7 @@ export function createActiveTimerCommands(
|
||||
{
|
||||
id: 'timer-toggle-billable',
|
||||
label: 'Toggle Billable',
|
||||
icon: CurrencyDollarIcon,
|
||||
icon: BillableIcon,
|
||||
keywords: ['billable', 'non-billable', 'money'],
|
||||
group: 'active-timer',
|
||||
action: activeTimerActions.toggleBillable,
|
||||
|
||||
Reference in New Issue
Block a user