mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-13 18:52:14 +01:00
Move tabs and TabBar into UI package
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from '..';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
|
||||
import { Tabs, TabsList } from '@/Components/ui/tabs';
|
||||
import TabBarItem from '@/Components/Common/TabBar/TabBarItem.vue';
|
||||
import { Tabs, TabsList } from '../tabs';
|
||||
import TabBarItem from '../TabBar/TabBarItem.vue';
|
||||
import CalendarSettingsPopover from './CalendarSettingsPopover.vue';
|
||||
import type { CalendarSettings } from './calendarSettings';
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ const props = defineProps<{
|
||||
enableEstimatedTime: boolean;
|
||||
currency: string;
|
||||
canCreateProject: boolean;
|
||||
organizationBillableRate: number | null;
|
||||
|
||||
createTimeEntry: (
|
||||
entry: Omit<TimeEntry, 'id' | 'organization_id' | 'user_id'>
|
||||
@@ -417,6 +418,7 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
|
||||
:create-tag="createTag"
|
||||
:currency="currency"
|
||||
:can-create-project="canCreateProject"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:tags="tags as any"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
@@ -438,7 +440,8 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
|
||||
:tasks="tasks"
|
||||
:clients="clients"
|
||||
:currency="currency"
|
||||
:can-create-project="canCreateProject" />
|
||||
:can-create-project="canCreateProject"
|
||||
:organization-billable-rate="organizationBillableRate" />
|
||||
|
||||
<template v-if="!loading">
|
||||
<CalendarToolbar
|
||||
|
||||
@@ -26,6 +26,7 @@ const props = defineProps<{
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
organizationBillableRate: number | null;
|
||||
}>();
|
||||
|
||||
const activeClients = computed(() => {
|
||||
@@ -113,7 +114,10 @@ const currentClientName = computed(() => {
|
||||
<ProjectEditBillableSection
|
||||
v-model:is-billable="project.is_billable"
|
||||
v-model:billable-rate="project.billable_rate"
|
||||
:currency="currency"></ProjectEditBillableSection>
|
||||
:currency="currency"
|
||||
:organization-billable-rate="
|
||||
organizationBillableRate
|
||||
"></ProjectEditBillableSection>
|
||||
<EstimatedTimeSection
|
||||
v-if="enableEstimatedTime"
|
||||
v-model="project.estimated_time"
|
||||
|
||||
@@ -10,15 +10,12 @@ import {
|
||||
} from '@/packages/ui/src/tooltip';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
|
||||
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
}>();
|
||||
|
||||
const { organization } = useOrganizationQuery(getCurrentOrganizationId()!);
|
||||
|
||||
type RateType = 'default-rate' | 'custom-rate';
|
||||
|
||||
const billableDefault = ref<'billable' | 'non-billable'>('non-billable');
|
||||
@@ -49,7 +46,7 @@ watch(rateType, () => {
|
||||
billableDefault.value = 'billable';
|
||||
isBillable.value = true;
|
||||
if (!billableRate.value) {
|
||||
billableRate.value = organization.value?.billable_rate ?? null;
|
||||
billableRate.value = props.organizationBillableRate ?? null;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -57,7 +54,7 @@ watch(rateType, () => {
|
||||
const displayedRate = computed({
|
||||
get() {
|
||||
if (rateType.value === 'default-rate') {
|
||||
return organization.value?.billable_rate ?? null;
|
||||
return props.organizationBillableRate ?? null;
|
||||
}
|
||||
return billableRate.value;
|
||||
},
|
||||
|
||||
17
resources/js/packages/ui/src/TabBar/TabBar.vue
Normal file
17
resources/js/packages/ui/src/TabBar/TabBar.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Tabs, TabsList } from '../tabs';
|
||||
|
||||
defineProps<{
|
||||
defaultValue?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tabs :default-value="defaultValue" class="w-full">
|
||||
<TabsList class="flex items-center space-x-0.5 sm:space-x-1">
|
||||
<slot></slot>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
27
resources/js/packages/ui/src/TabBar/TabBarItem.vue
Normal file
27
resources/js/packages/ui/src/TabBar/TabBarItem.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { TabsTrigger } from '../tabs';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value: string;
|
||||
class?: string;
|
||||
icon?: Component;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsTrigger
|
||||
:value="value"
|
||||
:icon="icon"
|
||||
:class="
|
||||
twMerge(
|
||||
'rounded-md px-2 sm:px-3 border py-1.5 text-xs sm:text-sm font-medium text-text-tertiary hover:text-text-primary focus-visible:outline-none data-[state=active]:bg-tab-background data-[state=active]:border-input-border data-[state=active]:text-text-primary border-tab-border',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<slot></slot>
|
||||
</TabsTrigger>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -46,6 +46,7 @@ const props = defineProps<{
|
||||
updateTimeEntry: (timeEntry: TimeEntry) => void;
|
||||
deleteTimeEntries: (timeEntries: TimeEntry[]) => void;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
selectedTimeEntries: TimeEntry[];
|
||||
enableEstimatedTime: boolean;
|
||||
canCreateProject: boolean;
|
||||
@@ -136,6 +137,7 @@ function onSelectChange(checked: boolean) {
|
||||
:project="timeEntry.project_id"
|
||||
:enable-estimated-time
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:task="timeEntry.task_id"
|
||||
@changed="
|
||||
updateProjectAndTask
|
||||
@@ -240,6 +242,7 @@ function onSelectChange(checked: boolean) {
|
||||
:project="timeEntry.project_id"
|
||||
:enable-estimated-time
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:task="timeEntry.task_id"
|
||||
@changed="
|
||||
updateProjectAndTask
|
||||
@@ -291,6 +294,7 @@ function onSelectChange(checked: boolean) {
|
||||
:create-client
|
||||
:clients
|
||||
:create-project
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:tags="tags"
|
||||
indent
|
||||
:update-time-entry="(timeEntry: TimeEntry) => updateTimeEntry(timeEntry)"
|
||||
|
||||
@@ -42,6 +42,7 @@ const props = defineProps<{
|
||||
start?: string;
|
||||
end?: string;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
canCreateProject: boolean;
|
||||
}>();
|
||||
|
||||
@@ -165,6 +166,7 @@ const billableProxy = computed({
|
||||
:create-client
|
||||
:can-create-project
|
||||
:currency
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:enable-estimated-time="enableEstimatedTime" />
|
||||
|
||||
@@ -43,6 +43,7 @@ const props = defineProps<{
|
||||
tasks: Task[];
|
||||
clients: Client[];
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
canCreateProject: boolean;
|
||||
}>();
|
||||
|
||||
@@ -172,6 +173,7 @@ const billableProxy = computed({
|
||||
:create-client
|
||||
:can-create-project="canCreateProject"
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:enable-estimated-time="enableEstimatedTime" />
|
||||
|
||||
@@ -34,6 +34,7 @@ const props = defineProps<{
|
||||
createProject: (project: CreateProjectBody) => Promise<Project | undefined>;
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
enableEstimatedTime: boolean;
|
||||
canCreateProject: boolean;
|
||||
}>();
|
||||
@@ -163,6 +164,7 @@ function unselectAllTimeEntries(value: TimeEntriesGroupedByType[]) {
|
||||
:delete-time-entries
|
||||
:create-tag
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:time-entry="entry"
|
||||
@selected="
|
||||
(timeEntries: TimeEntry[]) => {
|
||||
@@ -195,6 +197,7 @@ function unselectAllTimeEntries(value: TimeEntriesGroupedByType[]) {
|
||||
:tags="tags"
|
||||
:clients
|
||||
:create-tag
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:update-time-entry
|
||||
:on-start-stop-click="() => startTimeEntryFromExisting(entry)"
|
||||
:delete-time-entry="() => deleteTimeEntries([entry])"
|
||||
|
||||
@@ -33,6 +33,7 @@ const props = defineProps<{
|
||||
currency: string;
|
||||
enableEstimatedTime: boolean;
|
||||
canCreateProject: boolean;
|
||||
organizationBillableRate: number | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -58,6 +59,7 @@ const showMassUpdateModal = ref(false);
|
||||
:enable-estimated-time
|
||||
:can-create-project
|
||||
:currency
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:time-entries="selectedTimeEntries"
|
||||
@submit="emit('submit')"></TimeEntryMassUpdateModal>
|
||||
<MainContainer
|
||||
|
||||
@@ -35,6 +35,7 @@ const props = defineProps<{
|
||||
createTag: (name: string) => Promise<Tag | undefined>;
|
||||
updateTimeEntries: (changeset: UpdateMultipleTimeEntriesChangeset) => Promise<void>;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
enableEstimatedTime: boolean;
|
||||
canCreateProject: boolean;
|
||||
}>();
|
||||
@@ -162,6 +163,7 @@ watch(removeAllTags, () => {
|
||||
:create-project
|
||||
:create-client
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:can-create-project
|
||||
empty-placeholder="Select project..."
|
||||
allow-reset
|
||||
|
||||
@@ -46,6 +46,7 @@ const props = defineProps<{
|
||||
duplicateTimeEntry?: () => void;
|
||||
updateTimeEntry: (timeEntry: TimeEntry) => void;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
showMember?: boolean;
|
||||
showDate?: boolean;
|
||||
selected?: boolean;
|
||||
@@ -142,6 +143,7 @@ async function handleDeleteTimeEntry() {
|
||||
:tasks="tasks"
|
||||
:project="timeEntry.project_id"
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:enable-estimated-time
|
||||
:task="timeEntry.task_id"
|
||||
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
|
||||
@@ -209,6 +211,7 @@ async function handleDeleteTimeEntry() {
|
||||
:tasks="tasks"
|
||||
:project="timeEntry.project_id"
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:enable-estimated-time
|
||||
:task="timeEntry.task_id"
|
||||
@changed="
|
||||
@@ -277,6 +280,7 @@ async function handleDeleteTimeEntry() {
|
||||
:tasks="tasks"
|
||||
:clients="clients"
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:can-create-project="canCreateProject" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ const props = defineProps<{
|
||||
createClient: (client: CreateClientBody) => Promise<Client | undefined>;
|
||||
isActive: boolean;
|
||||
currency: string;
|
||||
organizationBillableRate: number | null;
|
||||
enableEstimatedTime: boolean;
|
||||
canCreateProject: boolean;
|
||||
}>();
|
||||
@@ -262,6 +263,7 @@ useSelectEvents(
|
||||
:clients
|
||||
:create-project
|
||||
:currency="currency"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:projects="projects"
|
||||
:tasks="tasks"
|
||||
:enable-estimated-time="enableEstimatedTime"
|
||||
|
||||
@@ -58,6 +58,7 @@ const props = withDefaults(
|
||||
emptyPlaceholder?: string;
|
||||
allowReset?: boolean;
|
||||
enableEstimatedTime: boolean;
|
||||
organizationBillableRate: number | null;
|
||||
canCreateProject: boolean;
|
||||
class?: string;
|
||||
variant?: 'input' | 'ghost' | 'outline';
|
||||
@@ -660,6 +661,7 @@ const showCreateProject = ref(false);
|
||||
v-model:show="showCreateProject"
|
||||
:create-client
|
||||
:enable-estimated-time="enableEstimatedTime"
|
||||
:organization-billable-rate="organizationBillableRate"
|
||||
:currency="currency"
|
||||
:clients="clients"
|
||||
:create-project></ProjectCreateModal>
|
||||
|
||||
@@ -145,6 +145,9 @@ import {
|
||||
SelectValue,
|
||||
} from './select/index';
|
||||
import { Separator } from './separator/index';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs/index';
|
||||
import TabBar from './TabBar/TabBar.vue';
|
||||
import TabBarItem from './TabBar/TabBarItem.vue';
|
||||
import TimezoneMismatchModal from './TimezoneMismatchModal.vue';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip/index';
|
||||
|
||||
@@ -257,6 +260,12 @@ export {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Separator,
|
||||
TabBar,
|
||||
TabBarItem,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
TextInput,
|
||||
time,
|
||||
TimeEntryCalendar,
|
||||
|
||||
15
resources/js/packages/ui/src/tabs/Tabs.vue
Normal file
15
resources/js/packages/ui/src/tabs/Tabs.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { TabsRootEmits, TabsRootProps } from 'reka-ui';
|
||||
import { TabsRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<TabsRootProps>();
|
||||
const emits = defineEmits<TabsRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsRoot v-bind="forwarded">
|
||||
<slot />
|
||||
</TabsRoot>
|
||||
</template>
|
||||
26
resources/js/packages/ui/src/tabs/TabsContent.vue
Normal file
26
resources/js/packages/ui/src/tabs/TabsContent.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '../utils/cn';
|
||||
import { TabsContent, type TabsContentProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<TabsContentProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsContent
|
||||
:class="
|
||||
cn(
|
||||
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
v-bind="delegatedProps">
|
||||
<slot />
|
||||
</TabsContent>
|
||||
</template>
|
||||
21
resources/js/packages/ui/src/tabs/TabsList.vue
Normal file
21
resources/js/packages/ui/src/tabs/TabsList.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '../utils/cn';
|
||||
import { TabsList, type TabsListProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsList
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('inline-flex items-center rounded-lg text-muted-foreground', props.class)">
|
||||
<slot />
|
||||
</TabsList>
|
||||
</template>
|
||||
35
resources/js/packages/ui/src/tabs/TabsTrigger.vue
Normal file
35
resources/js/packages/ui/src/tabs/TabsTrigger.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '../utils/cn';
|
||||
import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes, type Component } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
TabsTriggerProps & { class?: HTMLAttributes['class']; icon?: Component }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
|
||||
props.class
|
||||
)
|
||||
">
|
||||
<div v-if="props.icon" class="mr-2 h-4 w-4">
|
||||
<props.icon />
|
||||
</div>
|
||||
<span class="truncate">
|
||||
<slot />
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</template>
|
||||
4
resources/js/packages/ui/src/tabs/index.ts
Normal file
4
resources/js/packages/ui/src/tabs/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Tabs } from './Tabs.vue';
|
||||
export { default as TabsContent } from './TabsContent.vue';
|
||||
export { default as TabsList } from './TabsList.vue';
|
||||
export { default as TabsTrigger } from './TabsTrigger.vue';
|
||||
Reference in New Issue
Block a user