mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
fix "No project" duplicating rows, unify no project senitel to null
This commit is contained in:
@@ -123,6 +123,7 @@ const emit = defineEmits<{
|
|||||||
:create-project="createProject"
|
:create-project="createProject"
|
||||||
:create-client="createClient"
|
:create-client="createClient"
|
||||||
:organization-billable-rate="organization?.billable_rate ?? null"
|
:organization-billable-rate="organization?.billable_rate ?? null"
|
||||||
|
:no-project-value="null"
|
||||||
@changed="(p, t) => emit('add-row', p, t)">
|
@changed="(p, t) => emit('add-row', p, t)">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<Button variant="ghost" size="sm" class="text-text-secondary">
|
<Button variant="ghost" size="sm" class="text-text-secondary">
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ function hasRunningEntry(dayIndex: number): boolean {
|
|||||||
:create-project="createProject"
|
:create-project="createProject"
|
||||||
:create-client="createClient"
|
:create-client="createClient"
|
||||||
:organization-billable-rate="organization?.billable_rate ?? null"
|
:organization-billable-rate="organization?.billable_rate ?? null"
|
||||||
|
:no-project-value="null"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="w-full" />
|
class="w-full" />
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable vue/one-component-per-file */
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { defineComponent, h, nextTick, onMounted } from 'vue';
|
||||||
|
import TimeTrackerProjectTaskDropdown from './TimeTrackerProjectTaskDropdown.vue';
|
||||||
|
import type { Client, Project, Task } from '@/packages/api/src';
|
||||||
|
|
||||||
|
const DropdownStub = defineComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(_, { emit, slots }) {
|
||||||
|
onMounted(() => emit('update:modelValue', true));
|
||||||
|
return () => h('div', [slots.trigger?.(), slots.content?.()]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const FocusTrapStub = defineComponent({
|
||||||
|
setup(_, { slots }) {
|
||||||
|
return () => h('div', slots.default?.());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function mountDropdown(props: Record<string, unknown> = {}) {
|
||||||
|
return mount(TimeTrackerProjectTaskDropdown, {
|
||||||
|
props: {
|
||||||
|
project: null,
|
||||||
|
task: null,
|
||||||
|
projects: [] as Project[],
|
||||||
|
tasks: [] as Task[],
|
||||||
|
clients: [] as Client[],
|
||||||
|
createProject: vi.fn(),
|
||||||
|
createClient: vi.fn(),
|
||||||
|
currency: 'EUR',
|
||||||
|
enableEstimatedTime: false,
|
||||||
|
organizationBillableRate: null,
|
||||||
|
canCreateProject: false,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
Dropdown: DropdownStub,
|
||||||
|
UseFocusTrap: FocusTrapStub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDropdown() {
|
||||||
|
const wrapper = mountDropdown();
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TimeTrackerProjectTaskDropdown', () => {
|
||||||
|
it('keeps the existing empty-string no-project value by default', async () => {
|
||||||
|
const wrapper = await openDropdown();
|
||||||
|
|
||||||
|
await wrapper.find('[data-project-id=""]').trigger('click');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:project')?.at(-1)).toEqual(['']);
|
||||||
|
expect(wrapper.emitted('changed')?.at(-1)).toEqual(['', null]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can emit null for no-project consumers that use null as the domain value', async () => {
|
||||||
|
const wrapper = mountDropdown({ project: 'p-1', noProjectValue: null });
|
||||||
|
await nextTick();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
await wrapper.find('[data-project-id=""]').trigger('click');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:project')?.at(-1)).toEqual([null]);
|
||||||
|
expect(wrapper.emitted('changed')?.at(-1)).toEqual([null, null]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,6 +16,8 @@ import ProjectCreateModal from '@/packages/ui/src/Project/ProjectCreateModal.vue
|
|||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import { Button } from '@/packages/ui/src/Buttons';
|
import { Button } from '@/packages/ui/src/Buttons';
|
||||||
|
|
||||||
|
const NO_PROJECT_ID = '';
|
||||||
|
|
||||||
const task = defineModel<string | null>('task', {
|
const task = defineModel<string | null>('task', {
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
@@ -57,6 +59,7 @@ const props = withDefaults(
|
|||||||
currency: string;
|
currency: string;
|
||||||
emptyPlaceholder?: string;
|
emptyPlaceholder?: string;
|
||||||
allowReset?: boolean;
|
allowReset?: boolean;
|
||||||
|
noProjectValue?: string | null;
|
||||||
enableEstimatedTime: boolean;
|
enableEstimatedTime: boolean;
|
||||||
organizationBillableRate: number | null;
|
organizationBillableRate: number | null;
|
||||||
canCreateProject: boolean;
|
canCreateProject: boolean;
|
||||||
@@ -68,6 +71,7 @@ const props = withDefaults(
|
|||||||
{
|
{
|
||||||
emptyPlaceholder: 'No Project',
|
emptyPlaceholder: 'No Project',
|
||||||
allowReset: false,
|
allowReset: false,
|
||||||
|
noProjectValue: NO_PROJECT_ID,
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
@@ -164,10 +168,10 @@ function updateFilteredResults() {
|
|||||||
is_archived: false,
|
is_archived: false,
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
id: '',
|
id: NO_PROJECT_ID,
|
||||||
name: 'No Project',
|
name: 'No Project',
|
||||||
color: 'var(--theme-color-icon-default)',
|
color: 'var(--theme-color-icon-default)',
|
||||||
value: '',
|
value: NO_PROJECT_ID,
|
||||||
client_id: null,
|
client_id: null,
|
||||||
billable_rate: null,
|
billable_rate: null,
|
||||||
is_archived: false,
|
is_archived: false,
|
||||||
@@ -490,7 +494,7 @@ function selectTask(taskId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectProject(projectId: string) {
|
function selectProject(projectId: string) {
|
||||||
project.value = projectId;
|
project.value = projectId === NO_PROJECT_ID ? props.noProjectValue : projectId;
|
||||||
task.value = null;
|
task.value = null;
|
||||||
open.value = false;
|
open.value = false;
|
||||||
searchValue.value = '';
|
searchValue.value = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user