Compare commits

...

8 Commits

138 changed files with 3510 additions and 3305 deletions

View File

@@ -1,13 +0,0 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
extends: ['plugin:vue/vue3-essential', '@vue/eslint-config-typescript/recommended', '@vue/eslint-config-prettier'],
rules: {
'vue/multi-word-component-names': 'off',
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": "error",
},
plugins: ['unused-imports'],
}

View File

@@ -109,7 +109,7 @@ services:
- sail
- reverse-proxy
playwright:
image: mcr.microsoft.com/playwright:v1.46.1-jammy
image: mcr.microsoft.com/playwright:v1.50.0-jammy
command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0']
working_dir: /src
extra_hosts:

View File

@@ -191,7 +191,7 @@ test('test that updating a the start of an existing time entry in the overview w
'time_entry_range_selector'
);
await timeEntryRangeElement.click();
await page.getByTestId('time_picker_input').first().fill('1');
await page.getByTestId('time_entry_range_start').first().fill('1');
await Promise.all([
page.waitForResponse(async (response) => {
return (
@@ -204,10 +204,7 @@ test('test that updating a the start of an existing time entry in the overview w
(await response.json()).data.end !== null
);
}),
page
.getByTestId('time_entry_range_end')
.getByTestId('time_picker_input')
.press('Enter'),
page.getByTestId('time_entry_range_end').press('Enter'),
]);
});

View File

@@ -152,7 +152,7 @@ test('test that starting and updating the time while running works', async ({
JSON.stringify([])
);
}),
page.getByTestId('time_entry_time').press('Tab'),
page.getByTestId('time_entry_time').press('Enter'),
]);
await expect(page.getByTestId('time_entry_time')).toHaveValue(/00:20/);

36
eslint.config.mjs Normal file
View File

@@ -0,0 +1,36 @@
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
import eslintPluginVue from 'eslint-plugin-vue';
import globals from 'globals';
import typescriptEslint from 'typescript-eslint';
import unusedImports from "eslint-plugin-unused-imports";
export default typescriptEslint.config(
{ ignores: ['*.d.ts', '**/coverage', '**/dist'] },
{
extends: [
eslint.configs.recommended,
...typescriptEslint.configs.recommended,
...eslintPluginVue.configs['flat/recommended'],
],
files: ['**/*.{ts,vue,js}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: globals.browser,
parserOptions: {
parser: typescriptEslint.parser,
},
},
plugins: {
"unused-imports": unusedImports,
},
rules: {
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": "error",
},
},
eslintConfigPrettier
);

3424
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,53 +4,60 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore resources/js",
"lint:fix": "eslint --fix --ext .js,.vue,.ts --ignore-path .gitignore resources/js",
"lint": "eslint resources/js",
"lint:fix": "eslint --fix resources/js",
"type-check": "vue-tsc --noEmit",
"test:e2e": "rm -rf test-results/.auth && npx playwright test",
"zod:generate": "npx openapi-zod-client http://localhost:80/docs/api.json --output resources/js/packages/api/src/openapi.json.client.ts --base-url /api"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0",
"@inertiajs/vue3": "^1.0.0",
"@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/node": "^20.11.5",
"@vitejs/plugin-vue": "^4.5.0",
"@types/node": "^22.10.10",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.20",
"axios": "^1.6.4",
"eslint-plugin-unused-imports": "^3.1.0",
"eslint-plugin-unused-imports": "^4.1.4",
"laravel-vite-plugin": "^1.0.0",
"openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47",
"postcss-nesting": "^12.1.5",
"tailwindcss": "^3.4.13",
"typescript": "^5.3.3",
"vite": "^5.0.0",
"vite-plugin-checker": "^0.7.2",
"vue": "^3.4.0",
"vue-tsc": "^2.0.28"
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vite-plugin-checker": "^0.8.0",
"vue": "^3.5.0",
"vue-tsc": "^2.2.0"
},
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/vue": "^1.0.6",
"@heroicons/vue": "^2.1.1",
"@rushstack/eslint-patch": "^1.7.0",
"@rushstack/eslint-patch": "^1.10.5",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-query": "^5.56.2",
"@tanstack/vue-query-devtools": "^5.58.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^11.1.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0",
"@vueuse/core": "^12.5.0",
"@vueuse/integrations": "^12.5.0",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"focus-trap": "^7.6.0",
"parse-duration": "^1.1.0",
"parse-duration": "^2.0.1",
"pinia": "^2.1.7",
"radix-vue": "^1.9.6",
"tailwind-merge": "^2.2.1",
"vue-echarts": "^6.7.2"
"vue-echarts": "^7.0.3"
},
"overrides": {
"vite-plugin-checker": {
"vue-tsc": "$vue-tsc"
}
}
}

View File

@@ -112,7 +112,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span>
</div>
</Link>
<button @click="hideBlackFridayBanner = true" class="p-1">
<button class="p-1" @click="hideBlackFridayBanner = true">
<XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button>
@@ -142,7 +142,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span>
</div>
</Link>
<button @click="hideTrialBanner = true" class="p-1">
<button class="p-1" @click="hideTrialBanner = true">
<XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button>
@@ -174,7 +174,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span>
</div>
</Link>
<button @click="hideBlockedBanner = true" class="p-1">
<button class="p-1" @click="hideBlockedBanner = true">
<XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button>
@@ -206,7 +206,7 @@ const showBlackFridayBanner = computed(() => {
<span>Upgrade now</span>
</div>
</Link>
<button @click="hideFreeUpgradeBanner = true" class="p-1">
<button class="p-1" @click="hideFreeUpgradeBanner = true">
<XMarkIcon
class="w-4 opacity-50 hover:opacity-100"></XMarkIcon>
</button>

View File

@@ -45,10 +45,10 @@ useFocus(clientNameInput, { initialValue: true });
v-model="client.name"
type="text"
placeholder="Client Name"
@keydown.enter="submit"
class="mt-1 block w-full"
required
autocomplete="clientName" />
autocomplete="clientName"
@keydown.enter="submit" />
</div>
</div>
</template>

View File

@@ -46,10 +46,10 @@ useFocus(clientNameInput, { initialValue: true });
v-model="clientBody.name"
type="text"
placeholder="Client Name"
@keydown.enter="submit"
class="mt-1 block w-full"
required
autocomplete="clientName" />
autocomplete="clientName"
@keydown.enter="submit" />
</div>
</div>
</template>

View File

@@ -23,28 +23,28 @@ const props = defineProps<{
<div class="min-w-[150px]">
<button
v-if="canUpdateClients()"
@click="emit('edit')"
:aria-label="'Edit Client ' + props.client.name"
data-testid="client_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click.prevent="emit('archive')"
v-if="canUpdateClients()"
:aria-label="'Archive Client ' + props.client.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('archive')">
<ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon>
<span>{{ client.is_archived ? 'Unarchive' : 'Archive' }}</span>
</button>
<button
v-if="canDeleteClients()"
@click="emit('delete')"
:aria-label="'Delete Client ' + props.client.name"
data-testid="client_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Client) {
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Client..."
search-placeholder="Search for a Client..."
:items="clients"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">

View File

@@ -25,18 +25,18 @@ const createClient = ref(false);
style="grid-template-columns: 1fr 150px 200px 80px">
<ClientTableHeading></ClientTableHeading>
<div
class="col-span-2 py-24 text-center"
v-if="clients.length === 0">
v-if="clients.length === 0"
class="col-span-2 py-24 text-center">
<UserCircleIcon
class="w-8 text-icon-default inline pb-2"></UserCircleIcon>
<h3 class="text-white font-semibold">No clients found</h3>
<p class="pb-5" v-if="canCreateClients()">
<p v-if="canCreateClients()" class="pb-5">
Create your first client now!
</p>
<SecondaryButton
v-if="canCreateClients()"
@click="createClient = true"
:icon="PlusIcon as Component"
@click="createClient = true"
>Create your First Client
</SecondaryButton>
</div>

View File

@@ -38,8 +38,8 @@ const showEditModal = ref(false);
<template>
<TableRow>
<ClientEditModal
:client="client"
v-model:show="showEditModal"></ClientEditModal>
v-model:show="showEditModal"
:client="client"></ClientEditModal>
<div
class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12">
<span>

View File

@@ -10,16 +10,16 @@ const emit = defineEmits<{
<template>
<MoreOptionsDropdown label="Actions for the invitation">
<button
@click="emit('resend')"
data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('resend')">
<ArrowPathIcon class="w-5 text-icon-active"></ArrowPathIcon>
<span>Resend Invitation</span>
</button>
<button
@click="emit('delete')"
data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -18,10 +18,10 @@ defineEmits<{
<template>
<BillableRateModal
@submit="$emit('submit')"
v-model:show="show"
v-model:saving="saving"
title="Update Member Billable Rate">
title="Update Member Billable Rate"
@submit="$emit('submit')">
<p class="py-1 text-center">
The billable rate of {{ memberName }} will be updated to
<strong>{{

View File

@@ -17,8 +17,8 @@ const model = defineModel<string>({
const props = withDefaults(
defineProps<{
hiddenMembers: ProjectMember[];
disabled: boolean;
hiddenMembers?: ProjectMember[];
disabled?: boolean;
}>(),
{
hiddenMembers: () => [] as ProjectMember[],
@@ -76,7 +76,7 @@ const currentValue = computed(() => {
:items="filteredMembers"
:get-key-from-item="(member) => member.id"
:get-name-for-item="(member) => member.name">
<template v-slot:trigger>
<template #trigger>
<Badge
tag="button"
class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
@@ -84,7 +84,7 @@ const currentValue = computed(() => {
<div v-if="currentValue" class="flex-1 truncate">
{{ currentValue }}
</div>
<div class="flex-1" v-else>Select a member...</div>
<div v-else class="flex-1">Select a member...</div>
<ChevronDownIcon class="w-4 text-muted"></ChevronDownIcon>
</Badge>
</template>

View File

@@ -108,11 +108,11 @@ const roleDescription = computed(() => {
v-model:saving="saving"
v-model:show="showBillableRateModal"
:member-name="member.name"
:newBillableRate="memberBody.billable_rate"
:new-billable-rate="memberBody.billable_rate"
@submit="submitBillableRate"></MemberBillableRateModal>
<MemberOwnershipTransferConfirmModal
:member-name="member.name"
v-model:show="showOwnershipTransferConfirmModal"
:member-name="member.name"
@submit="submit"></MemberOwnershipTransferConfirmModal>
<DialogModal closeable :show="show" @close="show = false">
<template #title>
@@ -127,9 +127,9 @@ const roleDescription = computed(() => {
<div>
<InputLabel for="role" value="Role" />
<MemberRoleSelect
v-model="memberBody.role"
class="mt-2"
name="role"
v-model="memberBody.role"></MemberRoleSelect>
name="role"></MemberRoleSelect>
</div>
<div class="flex-1 text-xs flex items-center pt-6">
<p>{{ roleDescription }}</p>
@@ -140,28 +140,28 @@ const roleDescription = computed(() => {
<div>
<InputLabel for="billableType" value="Billable" />
<MemberBillableSelect
class="mt-2"
name="billableType"
v-model="
billableRateSelect
"></MemberBillableSelect>
"
class="mt-2"
name="billableType"></MemberBillableSelect>
</div>
<div
class="flex-1"
v-if="billableRateSelect === 'custom-rate'">
v-if="billableRateSelect === 'custom-rate'"
class="flex-1">
<InputLabel
for="memberBillableRate"
class="mb-2"
value="Billable Rate" />
<BillableRateInput
v-model="
memberBody.billable_rate
"
focus
class="w-full"
:currency="getOrganizationCurrencyString()"
@keydown.enter="saveWithChecks()"
name="memberBillableRate"
v-model="
memberBody.billable_rate
"></BillableRateInput>
@keydown.enter="saveWithChecks()"></BillableRateInput>
</div>
</div>
</div>

View File

@@ -112,11 +112,11 @@ useFocus(clientNameInput, { initialValue: true });
v-if="isBillingActivated() && canManageBilling()"
href="/billing">
<PrimaryButton
type="button"
class="mt-6"
v-if="
isBillingActivated() && canUpdateOrganization()
">
"
type="button"
class="mt-6">
<CreditCardIcon class="w-5 h-5 me-2" />
Go to Billing
</PrimaryButton>
@@ -128,15 +128,15 @@ useFocus(clientNameInput, { initialValue: true });
<InputLabel for="email" value="Email" />
<TextInput
id="email"
name="email"
ref="memberEmailInput"
v-model="addTeamMemberForm.email"
name="email"
type="text"
placeholder="Member Email"
@keydown.enter="submit"
class="mt-1 block w-full"
required
autocomplete="memberName" />
autocomplete="memberName"
@keydown.enter="submit" />
<InputError :message="errors.email" class="mt-2" />
</div>

View File

@@ -20,19 +20,19 @@ const props = defineProps<{
<div class="min-w-[150px]">
<button
v-if="canUpdateMembers()"
@click="emit('edit')"
:aria-label="'Edit Member ' + props.member.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
v-if="canDeleteMembers()"
@click="emit('delete')"
:aria-label="'Delete Member ' + props.member.name"
data-testid="member_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Member) {
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Member..."
search-placeholder="Search for a Member..."
:items="members"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">

View File

@@ -36,9 +36,9 @@ const emit = defineEmits<{
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>
<PrimaryButton
class="ms-3"
@click="emit('submit')"
:class="{ 'opacity-25': saving }"
:disabled="saving">
:disabled="saving"
@click="emit('submit')">
Confirm Transfer
</PrimaryButton>
</template>

View File

@@ -89,8 +89,8 @@ async function invitePlaceholder(id: string) {
member.is_placeholder === true &&
canInvitePlaceholderMembers()
"
@click="invitePlaceholder(member.id)"
size="small"
@click="invitePlaceholder(member.id)"
>Invite</SecondaryButton
>
<MemberMoreOptionsDropdown
@@ -99,8 +99,8 @@ async function invitePlaceholder(id: string) {
@delete="removeMember"></MemberMoreOptionsDropdown>
</div>
<MemberEditModal
:member="member"
v-model:show="showEditMemberModal"></MemberEditModal>
v-model:show="showEditMemberModal"
:member="member"></MemberEditModal>
</TableRow>
</template>

View File

@@ -34,8 +34,8 @@
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
@click="show = false"
class="inline-flex rounded-md bg-card-background text-muted hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
class="inline-flex rounded-md bg-card-background text-muted hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
@click="show = false">
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button>

View File

@@ -17,10 +17,10 @@ defineEmits<{
<template>
<BillableRateModal
@submit="$emit('submit')"
v-model:show="show"
v-model:saving="saving"
title="Update Organization Billable Rate">
title="Update Organization Billable Rate"
@submit="$emit('submit')">
<p class="py-0.5 text-center">
The organization billable rate will be updated to
<strong>{{

View File

@@ -40,7 +40,7 @@ const shownProjects = computed(() => {
withDefaults(
defineProps<{
border: boolean;
border?: boolean;
}>(),
{
border: true,
@@ -123,17 +123,17 @@ function updateValue(project: Project) {
<template #content>
<ComboboxRoot
:open="open"
:modelValue="currentProject"
@update:modelValue="updateValue"
@update:searchTerm="(e) => console.log(e)"
:searchTerm="searchValue"
class="relative">
:model-value="currentProject"
:search-term="searchValue"
class="relative"
@update:model-value="updateValue"
@update:search-term="(e) => console.log(e)">
<ComboboxAnchor>
<ComboboxInput
@keydown.enter="addProjectIfNoneExists"
ref="searchInput"
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
placeholder="Search for a project..." />
placeholder="Search for a project..."
@keydown.enter="addProjectIfNoneExists" />
</ComboboxAnchor>
<ComboboxContent>
<ComboboxViewport

View File

@@ -90,8 +90,8 @@ async function submitBillableRate() {
<div class="text-center">
<InputLabel for="color" value="Color" />
<ProjectColorSelector
class="mt-1"
v-model="project.color"></ProjectColorSelector>
v-model="project.color"
class="mt-1"></ProjectColorSelector>
</div>
</div>
<div class="w-full">
@@ -102,18 +102,18 @@ async function submitBillableRate() {
v-model="project.name"
type="text"
placeholder="Project Name"
@keydown.enter="submit()"
class="mt-1 block w-full"
required
autocomplete="projectName" />
autocomplete="projectName"
@keydown.enter="submit()" />
</div>
<div class="">
<InputLabel for="client" value="Client" />
<ClientDropdown
:createClient
v-model="project.client_id"
:create-client
:clients="clients"
class="mt-1"
v-model="project.client_id">
class="mt-1">
<template #trigger>
<Badge
class="bg-input-background cursor-pointer hover:bg-tertiary"
@@ -133,18 +133,18 @@ async function submitBillableRate() {
<div class="lg:grid grid-cols-2 gap-12">
<div>
<ProjectEditBillableSection
@submit="submit"
:currency="getOrganizationCurrencyString()"
v-model:isBillable="project.is_billable"
v-model:billableRate="
v-model:is-billable="project.is_billable"
v-model:billable-rate="
project.billable_rate
"></ProjectEditBillableSection>
"
:currency="getOrganizationCurrencyString()"
@submit="submit"></ProjectEditBillableSection>
</div>
<div>
<EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()"
@submit="submit()"
v-model="project.estimated_time"></EstimatedTimeSection>
v-model="project.estimated_time"
@submit="submit()"></EstimatedTimeSection>
</div>
</div>
</template>
@@ -163,9 +163,9 @@ async function submitBillableRate() {
<ProjectBillableRateModal
v-model:show="showBillableRateModal"
:currency="getOrganizationCurrencyString()"
@submit="submitBillableRate"
:new-billable-rate="project.billable_rate"
:project-name="project.name"></ProjectBillableRateModal>
:project-name="project.name"
@submit="submitBillableRate"></ProjectBillableRateModal>
</template>
<style scoped></style>

View File

@@ -21,29 +21,29 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Project ' + props.project.name">
<div class="min-w-[150px]">
<button
@click.prevent="emit('edit')"
v-if="canUpdateProjects()"
:aria-label="'Edit Project ' + props.project.name"
data-testid="project_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click.prevent="emit('archive')"
v-if="canUpdateProjects()"
:aria-label="'Archive Project ' + props.project.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('archive')">
<ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon>
<span>{{ project.is_archived ? 'Unarchive' : 'Archive' }}</span>
</button>
<button
@click.prevent="emit('delete')"
v-if="canDeleteProjects()"
:aria-label="'Delete Project ' + props.project.name"
data-testid="project_delete"
v-if="canDeleteProjects()"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Project) {
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Project..."
search-placeholder="Search for a Project..."
:items="projects"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">

View File

@@ -44,12 +44,12 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
<template>
<ProjectCreateModal
:createProject
:createClient
v-model:show="showCreateProjectModal"
:create-project
:create-client
:currency="getOrganizationCurrencyString()"
:clients="clients"
:enableEstimatedTime="isAllowedToPerformPremiumAction"
v-model:show="showCreateProjectModal"></ProjectCreateModal>
:enable-estimated-time="isAllowedToPerformPremiumAction"></ProjectCreateModal>
<div class="flow-root max-w-[100vw] overflow-x-auto">
<div class="inline-block min-w-full align-middle">
<div
@@ -57,12 +57,12 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
class="grid min-w-full"
:style="gridTemplate">
<ProjectTableHeading
:showBillableRate="
:show-billable-rate="
props.showBillableRate
"></ProjectTableHeading>
<div
class="col-span-5 py-24 text-center"
v-if="projects.length === 0">
v-if="projects.length === 0"
class="col-span-5 py-24 text-center">
<FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold">
@@ -81,14 +81,14 @@ import { isAllowedToPerformPremiumAction } from '@/utils/billing';
</p>
<SecondaryButton
v-if="canCreateProjects()"
@click="showCreateProjectModal = true"
:icon="PlusIcon"
@click="showCreateProjectModal = true"
>Create your First Project
</SecondaryButton>
</div>
<template v-for="project in projects" :key="project.id">
<ProjectTableRow
:showBillableRate="props.showBillableRate"
:show-billable-rate="props.showBillableRate"
:project="project"></ProjectTableRow>
</template>
</div>

View File

@@ -19,8 +19,8 @@ defineProps<{
Progress
</div>
<div
class="px-3 py-1.5 text-left font-semibold text-white"
v-if="showBillableRate">
v-if="showBillableRate"
class="px-3 py-1.5 text-left font-semibold text-white">
Billable Rate
</div>
<div class="px-3 py-1.5 text-left font-semibold text-white">Status</div>

View File

@@ -83,8 +83,8 @@ const showEditProjectModal = ref(false);
</div>
<div class="whitespace-nowrap min-w-0 px-3 py-4 text-sm text-muted">
<div
class="overflow-ellipsis overflow-hidden"
v-if="project.client_id">
v-if="project.client_id"
class="overflow-ellipsis overflow-hidden">
{{ client?.name }}
</div>
<div v-else>No client</div>
@@ -106,8 +106,8 @@ const showEditProjectModal = ref(false);
<span v-else> -- </span>
</div>
<div
class="whitespace-nowrap px-3 py-4 text-sm text-muted"
v-if="showBillableRate">
v-if="showBillableRate"
class="whitespace-nowrap px-3 py-4 text-sm text-muted">
{{ billableRateInfo }}
</div>
<div

View File

@@ -18,10 +18,10 @@ defineEmits<{
<template>
<BillableRateModal
@submit="$emit('submit')"
v-model:show="show"
v-model:saving="saving"
title="Update Project Member Billable Rate">
title="Update Project Member Billable Rate"
@submit="$emit('submit')">
<p class="py-1 text-center">
The billable rate of {{ memberName }} will be updated to
<strong>{{

View File

@@ -52,16 +52,16 @@ useFocus(projectNameInput, { initialValue: true });
<div class="grid grid-cols-3 items-center space-x-4">
<div class="col-span-3 sm:col-span-2">
<MemberCombobox
:hidden-members="props.existingMembers"
v-model="projectMember.member_id"></MemberCombobox>
v-model="projectMember.member_id"
:hidden-members="props.existingMembers"></MemberCombobox>
</div>
<div class="col-span-3 sm:col-span-1 flex-1">
<BillableRateInput
name="billable_rate"
:currency="getOrganizationCurrencyString()"
v-model="
projectMember.billable_rate
"></BillableRateInput>
"
name="billable_rate"
:currency="getOrganizationCurrencyString()"></BillableRateInput>
</div>
</div>
</template>

View File

@@ -75,8 +75,8 @@ useFocus(projectNameInput, { initialValue: true });
<template #content>
<ProjectMemberBillableRateModal
:member-name="props.name"
v-model:show="showBillableRateModal"
:member-name="props.name"
:new-billable-rate="projectMemberBody.billable_rate"
@close="showBillableRateModal = false"
@submit="submitBillableRate"></ProjectMemberBillableRateModal>
@@ -92,12 +92,12 @@ useFocus(projectNameInput, { initialValue: true });
class="mb-2"
value="Billable Rate"></InputLabel>
<BillableRateInput
@keydown.enter="submit"
:currency="getOrganizationCurrencyString()"
name="billable_rate"
v-model="
projectMemberBody.billable_rate
"></BillableRateInput>
"
:currency="getOrganizationCurrencyString()"
name="billable_rate"
@keydown.enter="submit"></BillableRateInput>
</div>
</div>
</template>

View File

@@ -27,17 +27,17 @@ const currentMember = computed(() => {
<MoreOptionsDropdown
:label="'Actions for Project Member ' + currentMember?.name">
<button
@click.prevent="emit('edit')"
:aria-label="'Edit Project Member ' + currentMember?.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('edit')">
<PencilSquareIcon class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click.prevent="emit('delete')"
:aria-label="'Delete Project Member ' + currentMember?.name"
data-testid="project_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Remove from Team</span>
</button>

View File

@@ -18,9 +18,9 @@ const createProjectMember = ref(false);
<template>
<ProjectMemberCreateModal
v-model:show="createProjectMember"
:existing-members="projectMembers"
:project-id="projectId"
v-model:show="createProjectMember"></ProjectMemberCreateModal>
:project-id="projectId"></ProjectMemberCreateModal>
<div class="flow-root">
<div class="inline-block min-w-full align-middle">
<div
@@ -29,15 +29,15 @@ const createProjectMember = ref(false);
style="grid-template-columns: 1fr 150px 150px 80px">
<ProjectMemberTableHeading></ProjectMemberTableHeading>
<div
class="col-span-5 py-24 text-center"
v-if="projectMembers.length === 0">
v-if="projectMembers.length === 0"
class="col-span-5 py-24 text-center">
<UserGroupIcon
class="w-8 text-icon-default inline pb-2"></UserGroupIcon>
<h3 class="text-white font-semibold">No project members</h3>
<p class="pb-5">Add the first project member!</p>
<SecondaryButton
@click="createProjectMember = true"
:icon="PlusIcon"
@click="createProjectMember = true"
>Add a new Project Member
</SecondaryButton>
</div>

View File

@@ -37,8 +37,8 @@ const showEditModal = ref(false);
<template>
<TableRow>
<ProjectMemberEditModal
:name="member?.name"
v-model:show="showEditModal"
:name="member?.name"
:project-member="projectMember"></ProjectMemberEditModal>
<div
class="whitespace-nowrap flex items-center space-x-5 3xl:pl-12 py-4 pr-3 text-sm font-medium text-white pl-4 sm:pl-6 lg:pl-8 3xl:pl-12">

View File

@@ -82,22 +82,22 @@ async function submit() {
<InputLabel for="name" value="Name" />
<TextInput
id="name"
class="mt-1.5 w-full"
v-model="report.name"></TextInput>
v-model="report.name"
class="mt-1.5 w-full"></TextInput>
</div>
<div>
<InputLabel for="description" value="Description" />
<TextInput
id="description"
class="mt-1.5 w-full"
v-model="report.description"></TextInput>
v-model="report.description"
class="mt-1.5 w-full"></TextInput>
</div>
<InputLabel value="Visibility" />
<div class="flex items-center space-x-12">
<div class="flex items-center space-x-3 px-2 py-3">
<Checkbox
v-model:checked="report.is_public"
id="is_public"></Checkbox>
id="is_public"
v-model:checked="report.is_public"></Checkbox>
<InputLabel for="is_public" value="Public" />
</div>
<div

View File

@@ -96,22 +96,22 @@ async function submit() {
<InputLabel for="name" value="Name" />
<TextInput
id="name"
class="mt-1.5 w-full"
v-model="report.name"></TextInput>
v-model="report.name"
class="mt-1.5 w-full"></TextInput>
</div>
<div>
<InputLabel for="description" value="Description" />
<TextInput
id="description"
class="mt-1.5 w-full"
v-model="report.description"></TextInput>
v-model="report.description"
class="mt-1.5 w-full"></TextInput>
</div>
<InputLabel value="Visibility" />
<div class="flex items-center space-x-12">
<div class="flex items-center space-x-2 px-2 py-3">
<Checkbox
v-model:checked="report.is_public"
id="is_public"></Checkbox>
id="is_public"
v-model:checked="report.is_public"></Checkbox>
<InputLabel for="is_public" value="Public" />
</div>
<div

View File

@@ -17,19 +17,19 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Project ' + props.report.name">
<div class="min-w-[150px]">
<button
@click.prevent="emit('edit')"
v-if="canUpdateReport()"
:aria-label="'Edit Report ' + props.report.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click.prevent="emit('delete')"
:aria-label="'Delete Report ' + props.report.name"
v-if="canDeleteReport()"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
:aria-label="'Delete Report ' + props.report.name"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click.prevent="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -27,8 +27,8 @@ function onSaveReportClick() {
<template>
<ReportCreateModal
:properties="reportProperties"
v-model:show="showCreateReportModal"></ReportCreateModal>
v-model:show="showCreateReportModal"
:properties="reportProperties"></ReportCreateModal>
<UpgradeModal v-model:show="showPremiumModal">
<strong>Sharable Reports</strong> is only available in solidtime
Professional.

View File

@@ -27,19 +27,19 @@ const gridTemplate = computed(() => {
:style="gridTemplate">
<ReportTableHeading></ReportTableHeading>
<div
class="col-span-5 py-24 text-center"
v-if="reports.length === 0">
v-if="reports.length === 0"
class="col-span-5 py-24 text-center">
<FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold">
No shared reports found
</h3>
<p class="pb-5" v-if="canCreateProjects()">
<p v-if="canCreateProjects()" class="pb-5">
Create your first project now!
</p>
<SecondaryButton
@click="router.visit(route('reporting'))"
:icon="PlusIcon"
@click="router.visit(route('reporting'))"
>Go to the overview to create a report
</SecondaryButton>
</div>

View File

@@ -87,7 +87,7 @@ async function deleteReport() {
<span v-else>Copied!</span>
</SecondaryButton>
<button
class="outline-0 focus-visible:ring-2 w-6 h-6 flex items-center justify-center rounded focus-visible:ring-white/80"
class="outline-0 focus-visible:ring-2 w-6 h-6 flex items-center justify-center rounded focus-visible:ring-ring"
@click="openSharableLink">
<ArrowTopRightOnSquareIcon
class="w-4 text-text-tertiary hover:text-text-secondary transition"></ArrowTopRightOnSquareIcon>

View File

@@ -157,7 +157,7 @@ const option = ref({
:autoresize="true"
class="chart"
:option="option" />
<div class="chart flex flex-col items-center justify-center" v-else>
<div v-else class="chart flex flex-col items-center justify-center">
<p class="text-lg text-white font-semibold">
No time entries found
</p>

View File

@@ -22,10 +22,10 @@ function downloadCurrentExport() {
<Modal
closeable
max-width="lg"
@close="showExportModal = false"
:show="showExportModal">
:show="showExportModal"
@close="showExportModal = false">
<button
class="text-text-tertiary w-6 mx-auto absolute focus-visible:outline-none focus-visible:ring-2 rounded-full focus-visible:ring-white/80 transition focus-visible:text-text-primary hover:text-text-primary top-2 right-2">
class="text-text-tertiary w-6 mx-auto absolute focus-visible:outline-none focus-visible:ring-2 rounded-full focus-visible:ring-ring transition focus-visible:text-text-primary hover:text-text-primary top-2 right-2">
<XMarkIcon @click="showExportModal = false"></XMarkIcon>
</button>
<div class="text-center text-text-primary py-6">

View File

@@ -21,6 +21,7 @@ const activeClass = computed(() => {
<template>
<Badge
size="large"
tag="button"
:class="
twMerge(
'cursor-pointer hover:bg-card-background transition flex',

View File

@@ -23,7 +23,7 @@ const title = computed(() => {
:get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label"
:items="groupByOptions">
<template v-slot:trigger>
<template #trigger>
<Badge
size="large"
class="cursor-pointer hover:bg-card-background transition space-x-5 flex">

View File

@@ -35,9 +35,9 @@ const expanded = ref(false);
)
">
<GroupedItemsCountButton
v-if="entry.grouped_data && entry.grouped_data?.length > 0"
:expanded="expanded"
@click="expanded = !expanded"
v-if="entry.grouped_data && entry.grouped_data?.length > 0">
@click="expanded = !expanded">
{{ entry.grouped_data?.length }}
</GroupedItemsCountButton>
<span>
@@ -52,13 +52,13 @@ const expanded = ref(false);
</div>
</div>
<div
v-if="expanded && entry.grouped_data"
class="col-span-3 grid bg-quaternary"
style="grid-template-columns: 1fr 150px 150px"
v-if="expanded && entry.grouped_data">
style="grid-template-columns: 1fr 150px 150px">
<ReportingRow
indent
v-for="subEntry in entry.grouped_data"
:key="subEntry.description ?? 'none'"
indent
:entry="subEntry"></ReportingRow>
</div>
</template>

View File

@@ -10,18 +10,18 @@ defineProps<{
<template>
<TabBar>
<TabBarItem
@click="router.visit(route('reporting'))"
:active="active === 'reporting'"
@click="router.visit(route('reporting'))"
>Overview</TabBarItem
>
<TabBarItem
@click="router.visit(route('reporting.detailed'))"
:active="active === 'detailed'"
@click="router.visit(route('reporting.detailed'))"
>Detailed</TabBarItem
>
<TabBarItem
@click="router.visit(route('reporting.shared'))"
:active="active === 'shared'"
@click="router.visit(route('reporting.shared'))"
>Shared</TabBarItem
>
</TabBar>

View File

@@ -14,10 +14,10 @@ const props = defineProps<{
<template>
<MoreOptionsDropdown :label="'Actions for Tag ' + props.tag.name">
<button
@click="emit('delete')"
:aria-label="'Delete Tag ' + props.tag.name"
data-testid="tag_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -19,8 +19,8 @@ const showCreateTagModal = ref(false);
<template>
<TagCreateModal
:createTag
v-model:show="showCreateTagModal"></TagCreateModal>
v-model:show="showCreateTagModal"
:create-tag></TagCreateModal>
<div class="flow-root">
<div class="inline-block min-w-full align-middle">
<div
@@ -29,18 +29,18 @@ const showCreateTagModal = ref(false);
style="grid-template-columns: 1fr 80px">
<TagTableHeading></TagTableHeading>
<div
class="col-span-5 py-24 text-center"
v-if="tags.length === 0">
v-if="tags.length === 0"
class="col-span-5 py-24 text-center">
<FolderPlusIcon
class="w-8 text-icon-default inline pb-2"></FolderPlusIcon>
<h3 class="text-white font-semibold">No tags found</h3>
<p class="pb-5" v-if="canCreateTags()">
<p v-if="canCreateTags()" class="pb-5">
Create your first tag now!
</p>
<SecondaryButton
v-if="canCreateTags()"
@click="showCreateTagModal = true"
:icon="PlusIcon"
@click="showCreateTagModal = true"
>Create your First Tag</SecondaryButton
>
</div>

View File

@@ -53,19 +53,19 @@ useFocus(taskNameInput, { initialValue: true });
v-model="taskName"
type="text"
placeholder="Task Name"
@keydown.enter="submit()"
class="mt-1 block w-full"
required
autocomplete="taskName" />
autocomplete="taskName"
@keydown.enter="submit()" />
</div>
<div class="col-span-6 sm:col-span-4">
<ProjectDropdown :modelValue="projectId"></ProjectDropdown>
<ProjectDropdown :model-value="projectId"></ProjectDropdown>
</div>
</div>
<EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()"
@submit="submit()"
v-model="estimatedTime"></EstimatedTimeSection>
v-model="estimatedTime"
@submit="submit()"></EstimatedTimeSection>
</template>
<template #footer>
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>

View File

@@ -50,16 +50,16 @@ useFocus(taskNameInput, { initialValue: true });
v-model="taskBody.name"
type="text"
placeholder="Task Name"
@keydown.enter="submit()"
class="mt-1 block w-full"
required
autocomplete="taskName" />
autocomplete="taskName"
@keydown.enter="submit()" />
</div>
</div>
<EstimatedTimeSection
v-if="isAllowedToPerformPremiumAction()"
@submit="submit()"
v-model="taskBody.estimated_time"></EstimatedTimeSection>
v-model="taskBody.estimated_time"
@submit="submit()"></EstimatedTimeSection>
</template>
<template #footer>
<SecondaryButton @click="show = false"> Cancel </SecondaryButton>

View File

@@ -21,30 +21,30 @@ const props = defineProps<{
<MoreOptionsDropdown :label="'Actions for Task ' + props.task.name">
<div class="min-w-[150px]">
<button
@click="emit('edit')"
v-if="canUpdateTasks()"
:aria-label="'Edit Task ' + props.task.name"
data-testid="task_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('edit')">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click="emit('done')"
v-if="canUpdateTasks()"
:aria-label="'Mark Task ' + props.task.name + ' as done'"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('done')">
<CheckCircleIcon class="w-5 text-icon-active"></CheckCircleIcon>
<span v-if="props.task.is_done">Mark as active</span>
<span v-else>Mark as done</span>
</button>
<button
@click="emit('delete')"
:aria-label="'Delete Task ' + props.task.name"
v-if="canDeleteTasks()"
:aria-label="'Delete Task ' + props.task.name"
data-testid="task_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('delete')">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>

View File

@@ -18,7 +18,7 @@ function getNameForItem(item: Task) {
<template>
<MultiselectDropdown
searchPlaceholder="Search for a Task..."
search-placeholder="Search for a Task..."
:items="tasks"
:get-key-from-item="getKeyFromItem"
:get-name-for-item="getNameForItem">

View File

@@ -19,8 +19,8 @@ const createTask = ref(false);
<template>
<TaskCreateModal
:project-id="props.projectId"
v-model:show="createTask"></TaskCreateModal>
v-model:show="createTask"
:project-id="props.projectId"></TaskCreateModal>
<div class="flow-root">
<div class="inline-block min-w-full align-middle">
<div
@@ -37,18 +37,18 @@ const createTask = ref(false);
">
<TaskTableHeading></TaskTableHeading>
<div
class="col-span-5 py-24 text-center"
v-if="tasks.length === 0">
v-if="tasks.length === 0"
class="col-span-5 py-24 text-center">
<PlusCircleIcon
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
<h3 class="text-white font-semibold">No tasks found</h3>
<p class="pb-5" v-if="canCreateTasks()">
<p v-if="canCreateTasks()" class="pb-5">
Create your first task now!
</p>
<SecondaryButton
v-if="canCreateTasks()"
@click="createTask = true"
:icon="PlusIcon"
@click="createTask = true"
>Create your First Task
</SecondaryButton>
</div>

View File

@@ -75,8 +75,8 @@ const showTaskEditModal = ref(false);
@delete="deleteTask"></TaskMoreOptionsDropdown>
</div>
<TaskEditModal
:task="task"
v-model:show="showTaskEditModal"></TaskEditModal>
v-model:show="showTaskEditModal"
:task="task"></TaskEditModal>
</TableRow>
</template>

View File

@@ -11,8 +11,8 @@ const showUpgradeModal = ref(false);
solidtime Professional.
</UpgradeModal>
<button
@click.prevent="showUpgradeModal = true"
class="inline-flex bg-secondary hover:bg-tertiary px-2 py-1 rounded border border-border-secondary hover:border-border-tertiary items-center space-x-1">
class="inline-flex bg-secondary hover:bg-tertiary px-2 py-1 rounded border border-border-secondary hover:border-border-tertiary items-center space-x-1"
@click.prevent="showUpgradeModal = true">
<LockClosedIcon class="w-3 text-text-tertiary"></LockClosedIcon>
<span class="text-xs text-text-secondary font-semibold"> Upgrade </span>
</button>

View File

@@ -50,12 +50,12 @@ const show = defineModel('show', { default: false });
v-if="isBillingActivated() && canManageBilling()"
href="/billing">
<PrimaryButton
type="button"
class="mt-6"
:icon="CreditCardIcon"
v-if="
isBillingActivated() && canUpdateOrganization()
">
"
type="button"
class="mt-6"
:icon="CreditCardIcon">
Go to Billing
</PrimaryButton>
</Link>

View File

@@ -32,8 +32,8 @@ const isRunningInDifferentOrganization = computed(() => {
<template>
<div class="pt-3 pb-2.5 px-2 flex justify-between items-center relative">
<div
class="absolute w-full h-full backdrop-blur-sm z-10 flex items-center justify-center"
v-if="isRunningInDifferentOrganization">
v-if="isRunningInDifferentOrganization"
class="absolute w-full h-full backdrop-blur-sm z-10 flex items-center justify-center">
<div
class="w-full h-[calc(100%+10px)] absolute bg-default-background opacity-75 backdrop-blur-sm"></div>
<div class="flex space-x-3 items-center w-full z-20 justify-center">
@@ -50,7 +50,7 @@ const isRunningInDifferentOrganization = computed(() => {
</div>
<TimeTrackerStartStop
:active="isActive"
@changed="setActiveState"
size="base"></TimeTrackerStartStop>
size="base"
@changed="setActiveState"></TimeTrackerStartStop>
</div>
</template>

View File

@@ -15,8 +15,8 @@ defineProps<{
<DashboardCard title="Last 7 Days" :icon="CalendarIcon">
<DayOverviewCardEntry
v-for="day in last7Days"
:class="last7Days.length === 7 ? 'last:border-0 first:pt-3' : ''"
:key="day.date"
:class="last7Days.length === 7 ? 'last:border-0 first:pt-3' : ''"
:date="day.date"
:history="day.history"
:duration="day.duration"></DayOverviewCardEntry>

View File

@@ -20,8 +20,8 @@ const props = defineProps<{
<DashboardCard title="Recently Tracked Tasks" :icon="CheckCircleIcon">
<RecentlyTrackedTasksCardEntry
v-for="lastTask in props.latestTasks"
:class="props.latestTasks.length === 4 ? 'last:border-0' : ''"
:key="lastTask.id"
:class="props.latestTasks.length === 4 ? 'last:border-0' : ''"
:project_id="lastTask.project_id"
:task_id="lastTask.id"
:title="lastTask.name"></RecentlyTrackedTasksCardEntry>

View File

@@ -29,14 +29,14 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
:icon
:current
:href></NavigationSidebarLink>
<CollapsibleRoot v-model:open="open" v-else
<CollapsibleRoot v-else v-model:open="open"
><CollapsibleTrigger class="w-full group py-0.5">
<div
class="text-muted group-hover:text-white group-hover:bg-menu-active group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center justify-between">
<div class="flex items-center gap-x-2">
<component
v-if="icon"
:is="icon"
v-if="icon"
:class="[
current
? 'text-icon-active'
@@ -59,8 +59,8 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
<CollapsibleContent class="CollapsibleContent">
<div class="px-3.5">
<ul
class="flex min-w-0 flex-col border-l border-border-secondary px-3 w-full my-0.5"
v-if="subItems">
v-if="subItems"
class="flex min-w-0 flex-col border-l border-border-secondary px-3 w-full my-0.5">
<li
v-for="subItem in subItems"
:key="subItem.title"

View File

@@ -19,8 +19,8 @@ defineProps<{
'group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center',
]">
<component
v-if="icon"
:is="icon"
v-if="icon"
:class="[
current
? 'text-icon-active'

View File

@@ -5,8 +5,8 @@
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<Notification
v-for="notification in notifications"
:type="notification.type"
:key="notification.uuid"
:type="notification.type"
:title="notification.title"
:message="notification.message"></Notification>
</div>

View File

@@ -110,29 +110,29 @@ const { tags } = storeToRefs(useTagsStore());
<CardTitle title="Time Tracker" :icon="ClockIcon"></CardTitle>
<div class="relative">
<TimeTrackerRunningInDifferentOrganizationOverlay
@switchOrganization="switchToTimeEntryOrganization"
v-if="
isRunningInDifferentOrganization
"></TimeTrackerRunningInDifferentOrganizationOverlay>
"
@switch-organization="switchToTimeEntryOrganization"></TimeTrackerRunningInDifferentOrganizationOverlay>
<TimeTrackerControls
:createProject
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:canCreateProject="canCreateProjects()"
:createClient
v-model:current-time-entry="currentTimeEntry"
v-model:live-timer="now"
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()"
:create-client
:clients
:tags
:tasks
:projects
:createTag
:isActive
:create-tag
:is-active
:currency="getOrganizationCurrencyString()"
v-model:currentTimeEntry="currentTimeEntry"
v-model:liveTimer="now"
@startLiveTimer="startLiveTimer"
@stopLiveTimer="stopLiveTimer"
@startTimer="setActiveState(true)"
@stopTimer="setActiveState(false)"
@updateTimeEntry="updateTimeEntry"></TimeTrackerControls>
@start-live-timer="startLiveTimer"
@stop-live-timer="stopLiveTimer"
@start-timer="setActiveState(true)"
@stop-timer="setActiveState(false)"
@update-time-entry="updateTimeEntry"></TimeTrackerControls>
</div>
</template>

View File

@@ -12,7 +12,7 @@ function openDesktopGithubRepo() {
</script>
<template>
<div class="py-4 hidden lg:block" v-if="showReleaseInfo">
<div v-if="showReleaseInfo" class="py-4 hidden lg:block">
<div
class="rounded-lg px-2.5 py-2 bg-card-background border border-border-secondary">
<div class="flex items-start justify-between">
@@ -23,8 +23,8 @@ function openDesktopGithubRepo() {
</div>
<button>
<XMarkIcon
@click="showReleaseInfo = false"
class="w-3.5 text-text-tertiary hover:text-text-secondary"></XMarkIcon>
class="w-3.5 text-text-tertiary hover:text-text-secondary"
@click="showReleaseInfo = false"></XMarkIcon>
</button>
</div>
@@ -34,9 +34,9 @@ function openDesktopGithubRepo() {
now.
</p>
<SecondaryButton
@click="openDesktopGithubRepo"
size="small"
class="w-full text-center justify-center mt-1.5"
@click="openDesktopGithubRepo"
>Download now</SecondaryButton
>
</div>

View File

@@ -85,8 +85,8 @@ const page = usePage<{
class="border-b border-default-background-separator pb-2 flex justify-between">
<OrganizationSwitcher class="w-full"></OrganizationSwitcher>
<XMarkIcon
@click="showSidebarMenu = false"
class="w-8 lg:hidden"></XMarkIcon>
class="w-8 lg:hidden"
@click="showSidebarMenu = false"></XMarkIcon>
</div>
<div class="border-b border-default-background-separator">
<CurrentSidebarTimer></CurrentSidebarTimer>
@@ -162,8 +162,8 @@ const page = usePage<{
route('clients')
"></NavigationSidebarItem>
<NavigationSidebarItem
title="Members"
v-if="canViewMembers()"
title="Members"
:icon="UserGroupIcon"
:current="route().current('members')"
:href="
@@ -238,8 +238,8 @@ const page = usePage<{
<div
class="lg:hidden w-full px-3 py-1 border-b border-b-default-background-separator text-muted flex justify-between items-center">
<Bars3Icon
@click="showSidebarMenu = !showSidebarMenu"
class="w-7 text-muted"></Bars3Icon>
class="w-7 text-muted"
@click="showSidebarMenu = !showSidebarMenu"></Bars3Icon>
<OrganizationSwitcher></OrganizationSwitcher>
</div>

View File

@@ -151,7 +151,7 @@ const page = usePage<{
</InputLabel>
</div>
<div class="mt-4" v-if="page.props.newsletter_consent">
<div v-if="page.props.newsletter_consent" class="mt-4">
<InputLabel for="newsletter_consent">
<div class="flex items-center">
<Checkbox

View File

@@ -74,7 +74,7 @@ function refreshDashboardData() {
<MainContainer
class="grid gap-5 sm:gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 pt-3 sm:pt-5 pb-4 sm:pb-6 border-b border-default-background-separator items-stretch">
<RecentlyTrackedTasksCard
:latestTasks="props.latestTasks"></RecentlyTrackedTasksCard>
:latest-tasks="props.latestTasks"></RecentlyTrackedTasksCard>
<LastSevenDaysCard
:last7-days="props.lastSevenDays"></LastSevenDaysCard>
<ActivityGraphCard
@@ -84,13 +84,13 @@ function refreshDashboardData() {
<TeamActivityCard
v-if="canViewMembers()"
class="flex lg:hidden xl:flex"
:latestTeamActivity="
:latest-team-activity="
props.latestTeamActivity
"></TeamActivityCard>
</MainContainer>
<MainContainer class="py-5">
<ThisWeekOverview
:weeklyProjectOverview="props.weeklyProjectOverview"
:weekly-project-overview="props.weeklyProjectOverview"
:total-weekly-billable-amount="props.totalWeeklyBillableAmount"
:total-weekly-billable-time="props.totalWeeklyBillableTime"
:total-weekly-time="props.totalWeeklyTime"

View File

@@ -52,8 +52,8 @@ function isActiveTab(tab: string) {
>Invite member</SecondaryButton
>
<MemberInviteModal
:available-roles="availableRoles"
v-model:show="inviteMember"
:available-roles="availableRoles"
@close="activeTab = 'invitations'"></MemberInviteModal>
</MainContainer>
<MemberTable v-if="activeTab === 'all'"></MemberTable>

View File

@@ -204,9 +204,9 @@ const page = usePage<{
<div class="col-span-6 sm:col-span-4">
<InputLabel for="timezone" value="Timezone" />
<select
name="timezone"
id="timezone"
v-model="form.timezone"
name="timezone"
required
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a Timezone</option>
@@ -225,9 +225,9 @@ const page = usePage<{
<div class="col-span-6 sm:col-span-4">
<InputLabel for="week_start" value="Start of the week" />
<select
name="week_start"
id="week_start"
v-model="form.week_start"
name="week_start"
required
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a week day</option>

View File

@@ -129,15 +129,15 @@ const shownTasks = computed(() => {
</nav>
<div>
<SecondaryButton
v-if="canCreateProjects()"
:icon="PencilSquareIcon"
@click="showEditProjectModal = true"
v-if="canCreateProjects()">
@click="showEditProjectModal = true">
Edit Project
</SecondaryButton>
<ProjectEditModal
v-if="project"
:originalProject="project"
v-model:show="showEditProjectModal"></ProjectEditModal>
v-model:show="showEditProjectModal"
:original-project="project"></ProjectEditModal>
</div>
</MainContainer>
<MainContainer>
@@ -168,8 +168,8 @@ const shownTasks = computed(() => {
>Create Task
</SecondaryButton>
<TaskCreateModal
:project-id="projectId"
v-model:show="createTask"></TaskCreateModal>
v-model:show="createTask"
:project-id="projectId"></TaskCreateModal>
</div>
</template>
</CardTitle>
@@ -188,11 +188,11 @@ const shownTasks = computed(() => {
Add Member
</SecondaryButton>
<ProjectMemberCreateModal
:project-id="projectId"
:existing-members="projectMembers"
v-model:show="
createProjectMember
"></ProjectMemberCreateModal>
"
:project-id="projectId"
:existing-members="projectMembers"></ProjectMemberCreateModal>
</template>
</CardTitle>
<Card>

View File

@@ -94,13 +94,13 @@ const showBillableRate = computed(() => {
>Create Project
</SecondaryButton>
<ProjectCreateModal
:createProject
:enableEstimatedTime="isAllowedToPerformPremiumAction"
:createClient
v-model:show="showCreateProjectModal"
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction"
:create-client
:currency="getOrganizationCurrencyString()"
:clients="clients"
@submit="createProject"
v-model:show="showCreateProjectModal"></ProjectCreateModal>
@submit="createProject"></ProjectCreateModal>
</MainContainer>
<ProjectTable
:show-billable-rate="showBillableRate"

View File

@@ -281,7 +281,7 @@ const tableData = computed(() => {
class="overflow-hidden">
<ReportingExportModal
v-model:show="showExportModal"
:exportUrl="exportUrl"></ReportingExportModal>
:export-url="exportUrl"></ReportingExportModal>
<MainContainer
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6">
@@ -292,7 +292,7 @@ const tableData = computed(() => {
<ReportingExportButton
:download="downloadExport"></ReportingExportButton>
<ReportSaveButton
:reportProperties="reportProperties"></ReportSaveButton>
:report-properties="reportProperties"></ReportSaveButton>
</div>
</MainContainer>
<div class="py-2.5 w-full border-b border-default-background-separator">
@@ -302,9 +302,9 @@ const tableData = computed(() => {
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
<div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown
@submit="updateReporting"
v-model="selectedMembers">
<template v-slot:trigger>
v-model="selectedMembers"
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedMembers.length"
:active="selectedMembers.length > 0"
@@ -313,9 +313,9 @@ const tableData = computed(() => {
</template>
</MemberMultiselectDropdown>
<ProjectMultiselectDropdown
@submit="updateReporting"
v-model="selectedProjects">
<template v-slot:trigger>
v-model="selectedProjects"
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedProjects.length"
:active="selectedProjects.length > 0"
@@ -324,9 +324,9 @@ const tableData = computed(() => {
</template>
</ProjectMultiselectDropdown>
<TaskMultiselectDropdown
@submit="updateReporting"
v-model="selectedTasks">
<template v-slot:trigger>
v-model="selectedTasks"
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedTasks.length"
:active="selectedTasks.length > 0"
@@ -335,20 +335,20 @@ const tableData = computed(() => {
</template>
</TaskMultiselectDropdown>
<ClientMultiselectDropdown
@submit="updateReporting"
v-model="selectedClients">
<template v-slot:trigger>
v-model="selectedClients"
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
title="Clients"
:icon="FolderIcon"></ReportingFilterBadge>
</template>
</ClientMultiselectDropdown>
<TagDropdown
@submit="updateReporting"
:createTag
v-model="selectedTags"
:tags="tags">
<template v-slot:trigger>
:create-tag
:tags="tags"
@submit="updateReporting">
<template #trigger>
<ReportingFilterBadge
:count="selectedTags.length"
:active="selectedTags.length > 0"
@@ -358,7 +358,6 @@ const tableData = computed(() => {
</TagDropdown>
<SelectDropdown
@changed="updateReporting"
v-model="billable"
:get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label"
@@ -375,8 +374,9 @@ const tableData = computed(() => {
label: 'Non Billable',
value: 'false',
},
]">
<template v-slot:trigger>
]"
@changed="updateReporting">
<template #trigger>
<ReportingFilterBadge
:active="billable !== null"
:title="
@@ -399,8 +399,8 @@ const tableData = computed(() => {
<MainContainer>
<div class="pt-10 w-full px-3 relative">
<ReportingChart
:groupedType="aggregatedGraphTimeEntries?.grouped_type"
:groupedData="
:grouped-type="aggregatedGraphTimeEntries?.grouped_type"
:grouped-data="
aggregatedGraphTimeEntries?.grouped_data
"></ReportingChart>
</div>
@@ -413,18 +413,18 @@ const tableData = computed(() => {
class="text-sm flex text-white items-center space-x-3 font-medium px-6 border-b border-card-background-separator pb-3">
<span>Group by</span>
<ReportingGroupBySelect
v-model="group"
:group-by-options="groupByOptions"
@changed="updateTableReporting"
v-model="group"></ReportingGroupBySelect>
@changed="updateTableReporting"></ReportingGroupBySelect>
<span>and</span>
<ReportingGroupBySelect
v-model="subGroup"
:group-by-options="
groupByOptions.filter(
(el) => el.value !== group
)
"
@changed="updateTableReporting"
v-model="subGroup"></ReportingGroupBySelect>
@changed="updateTableReporting"></ReportingGroupBySelect>
</div>
<div
class="grid items-center"
@@ -473,8 +473,8 @@ const tableData = computed(() => {
</div>
</template>
<div
class="chart flex flex-col items-center justify-center py-12 col-span-3"
v-else>
v-else
class="chart flex flex-col items-center justify-center py-12 col-span-3">
<p class="text-lg text-white font-semibold">
No time entries found
</p>

View File

@@ -252,7 +252,7 @@ async function downloadExport(format: ExportFormat) {
class="overflow-hidden">
<ReportingExportModal
v-model:show="showExportModal"
:exportUrl="exportUrl"></ReportingExportModal>
:export-url="exportUrl"></ReportingExportModal>
<MainContainer
class="py-3 sm:py-5 border-b border-default-background-separator flex justify-between items-center">
<div class="flex items-center space-x-3 sm:space-x-6">
@@ -270,9 +270,9 @@ async function downloadExport(format: ExportFormat) {
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
<div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown
@submit="updateFilteredTimeEntries"
v-model="selectedMembers">
<template v-slot:trigger>
v-model="selectedMembers"
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
:count="selectedMembers.length"
:active="selectedMembers.length > 0"
@@ -281,9 +281,9 @@ async function downloadExport(format: ExportFormat) {
</template>
</MemberMultiselectDropdown>
<ProjectMultiselectDropdown
@submit="updateFilteredTimeEntries"
v-model="selectedProjects">
<template v-slot:trigger>
v-model="selectedProjects"
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
:count="selectedProjects.length"
:active="selectedProjects.length > 0"
@@ -292,9 +292,9 @@ async function downloadExport(format: ExportFormat) {
</template>
</ProjectMultiselectDropdown>
<TaskMultiselectDropdown
@submit="updateFilteredTimeEntries"
v-model="selectedTasks">
<template v-slot:trigger>
v-model="selectedTasks"
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
:count="selectedTasks.length"
:active="selectedTasks.length > 0"
@@ -303,20 +303,20 @@ async function downloadExport(format: ExportFormat) {
</template>
</TaskMultiselectDropdown>
<ClientMultiselectDropdown
@submit="updateFilteredTimeEntries"
v-model="selectedClients">
<template v-slot:trigger>
v-model="selectedClients"
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
title="Clients"
:icon="FolderIcon"></ReportingFilterBadge>
</template>
</ClientMultiselectDropdown>
<TagDropdown
@submit="updateFilteredTimeEntries"
:createTag
v-model="selectedTags"
:tags="tags">
<template v-slot:trigger>
:create-tag
:tags="tags"
@submit="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
:count="selectedTags.length"
:active="selectedTags.length > 0"
@@ -326,7 +326,6 @@ async function downloadExport(format: ExportFormat) {
</TagDropdown>
<SelectDropdown
@changed="updateFilteredTimeEntries"
v-model="billable"
:get-key-from-item="(item) => item.value"
:get-name-for-item="(item) => item.label"
@@ -343,8 +342,9 @@ async function downloadExport(format: ExportFormat) {
label: 'Non Billable',
value: 'false',
},
]">
<template v-slot:trigger>
]"
@changed="updateFilteredTimeEntries">
<template #trigger>
<ReportingFilterBadge
:active="billable !== null"
:title="
@@ -366,12 +366,9 @@ async function downloadExport(format: ExportFormat) {
</div>
<TimeEntryMassActionRow
:selected-time-entries="selectedTimeEntries"
:canCreateProject="canCreateProjects()"
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
@submit="clearSelectionAndState"
:can-create-project="canCreateProjects()"
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:delete-selected="deleteSelected"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"
:all-selected="selectedTimeEntries.length === timeEntries.length"
:projects="projects"
:tasks="tasks"
@@ -387,34 +384,37 @@ async function downloadExport(format: ExportFormat) {
"
:create-project="createProject"
:create-client="createClient"
:createTag="createTag"></TimeEntryMassActionRow>
:create-tag="createTag"
@submit="clearSelectionAndState"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"></TimeEntryMassActionRow>
<div class="w-full relative">
<div v-for="entry in timeEntries" :key="entry.id">
<TimeEntryRow
:selected="selectedTimeEntries.includes(entry)"
@selected="selectedTimeEntries.push(entry)"
:canCreateProject="canCreateProjects()"
@unselected="
selectedTimeEntries = selectedTimeEntries.filter(
(item) => item.id !== entry.id
)
"
:createClient
:createProject
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()"
:create-client
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:projects="projects"
:tasks="tasks"
:tags="tags"
:clients
:createTag
:updateTimeEntry
:onStartStopClick="() => startTimeEntryFromExisting(entry)"
:deleteTimeEntry="() => deleteTimeEntries([entry])"
:create-tag
:update-time-entry
:on-start-stop-click="() => startTimeEntryFromExisting(entry)"
:delete-time-entry="() => deleteTimeEntries([entry])"
:currency="getOrganizationCurrencyString()"
:members="members"
showDate
showMember
:time-entry="entry"></TimeEntryRow>
show-date
show-member
:time-entry="entry"
@selected="selectedTimeEntries.push(entry)"
@unselected="
selectedTimeEntries = selectedTimeEntries.filter(
(item) => item.id !== entry.id
)
"></TimeEntryRow>
</div>
<div v-if="timeEntries.length === 0">
<div class="text-center pt-12">
@@ -431,10 +431,10 @@ async function downloadExport(format: ExportFormat) {
</div>
<PaginationRoot
v-model:page="currentPage"
:total="totalPages"
:items-per-page="pageLimit"
class="flex justify-center items-center py-8"
v-model:page="currentPage"
:sibling-count="1"
show-edges>
<PaginationList
@@ -485,13 +485,13 @@ async function downloadExport(format: ExportFormat) {
</template>
<style lang="postcss">
.navigation-item {
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring;
}
.pagination-item {
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring;
}
.pagination-item[data-selected] {
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-white/80 transition ease-in-out duration-150;
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-ring transition ease-in-out duration-150;
}
</style>

View File

@@ -101,12 +101,12 @@ watch(currentPage, () => {
v-if="isBillingActivated() && canManageBilling()"
href="/billing">
<PrimaryButton
type="button"
class="mt-6"
:icon="CreditCardIcon"
v-if="
isBillingActivated() && canUpdateOrganization()
">
"
type="button"
class="mt-6"
:icon="CreditCardIcon">
Go to Billing
</PrimaryButton>
</Link>
@@ -120,10 +120,10 @@ watch(currentPage, () => {
<PaginationRoot
v-if="reports.length > 0 || isAllowedToPerformPremiumAction()"
v-model:page="currentPage"
:total="totalPages"
:items-per-page="pageLimit"
class="flex justify-center items-center py-8"
v-model:page="currentPage"
:sibling-count="1"
show-edges>
<PaginationList
@@ -174,13 +174,13 @@ watch(currentPage, () => {
</template>
<style lang="postcss">
.navigation-item {
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
@apply bg-quaternary h-8 w-8 flex items-center justify-center rounded border border-border-primary text-text-tertiary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring;
}
.pagination-item {
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-white/80;
@apply bg-secondary h-8 w-8 flex items-center justify-center rounded border border-border-tertiary text-text-secondary hover:text-text-primary transition cursor-pointer hover:border-border-secondary hover:bg-secondary focus-visible:text-text-primary focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-ring;
}
.pagination-item[data-selected] {
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-white/80 transition ease-in-out duration-150;
@apply text-white bg-accent-300/10 border border-accent-300/20 rounded-md font-medium hover:bg-accent-300/20 active:bg-accent-300/20 outline-0 focus-visible:ring-2 focus:ring-ring transition ease-in-out duration-150;
}
</style>

View File

@@ -151,8 +151,8 @@ function getGroupLabel(key: string) {
<MainContainer>
<div class="pt-10 w-full px-3 relative">
<ReportingChart
:groupedType="aggregatedGraphTimeEntries?.grouped_type"
:groupedData="
:grouped-type="aggregatedGraphTimeEntries?.grouped_type"
:grouped-data="
aggregatedGraphTimeEntries?.grouped_data
"></ReportingChart>
</div>
@@ -217,8 +217,8 @@ function getGroupLabel(key: string) {
</div>
</template>
<div
class="chart flex flex-col items-center justify-center py-12 col-span-3"
v-else>
v-else
class="chart flex flex-col items-center justify-center py-12 col-span-3">
<p class="text-lg text-white font-semibold">
No time entries found
</p>

View File

@@ -31,9 +31,9 @@ async function createTag(tag: string) {
>Create Tag
</SecondaryButton>
<TagCreateModal
:createTag="createTag"
v-model:show="showCreateTagModal"></TagCreateModal>
v-model:show="showCreateTagModal"
:create-tag="createTag"></TagCreateModal>
</MainContainer>
<TagTable :createTag="createTag"></TagTable>
<TagTable :create-tag="createTag"></TagTable>
</AppLayout>
</template>

View File

@@ -191,9 +191,9 @@ const showResultModal = ref(false);
<div>
<InputLabel for="importType" value="Import Type" />
<select
name="importType"
id="importType"
v-model="importType"
name="importType"
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option :value="null" selected disabled>
Select an import type to get instructions...
@@ -206,8 +206,8 @@ const showResultModal = ref(false);
</option>
</select>
<div
class="py-3 text-white"
v-if="currentImporterDescription">
v-if="currentImporterDescription"
class="py-3 text-white">
<div class="font-semibold text-muted py-1">
Instructions:
</div>
@@ -233,12 +233,12 @@ const showResultModal = ref(false);
>Upload a Toggl/Clockify Export</span
>
<input
ref="importFile"
id="file-upload"
ref="importFile"
name="file-upload"
v-on:change="updateFiles"
type="file"
class="sr-only" />
class="sr-only"
@change="updateFiles" />
</label>
</div>
<p class="text-xs leading-5 text-muted">

View File

@@ -62,10 +62,10 @@ function checkForConfirmationModal() {
<template #form>
<OrganizationBillableRateModal
v-model:show="showConfirmationModal"
@submit="submit"
:new-billable-rate="
organizationBody.billable_rate
"></OrganizationBillableRateModal>
"
@submit="submit"></OrganizationBillableRateModal>
<!-- Organization Owner Information -->
<div class="col-span-6">
<div class="col-span-6 sm:col-span-4">
@@ -75,8 +75,8 @@ function checkForConfirmationModal() {
value="Organization Billable Rate" />
<BillableRateInput
v-if="organization"
:currency="getOrganizationCurrencyString()"
v-model="organizationBody.billable_rate"
:currency="getOrganizationCurrencyString()"
name="organizationBillableRate"></BillableRateInput>
</div>
</div>
@@ -86,10 +86,10 @@ function checkForConfirmationModal() {
<div class="flex items-center space-x-2">
<Checkbox
v-if="organization"
id="organizationShowBillableRatesToEmployees"
v-model:checked="
organizationBody.employees_can_see_billable_rates
"
id="organizationShowBillableRatesToEmployees"></Checkbox>
"></Checkbox>
<InputLabel
for="organizationShowBillableRatesToEmployees"
value="Show Billable Rates to Employees" />

View File

@@ -89,9 +89,9 @@ const updateTeamName = () => {
<div class="col-span-6 sm:col-span-4">
<InputLabel for="currency" value="Currency" />
<select
name="currency"
id="currency"
v-model="form.currency"
name="currency"
:disabled="!permissions.canUpdateTeam"
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
<option value="" disabled>Select a currency</option>

View File

@@ -120,16 +120,16 @@ function deleteSelected() {
<template>
<TimeEntryCreateModal
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:createProject="createProject"
:createClient="createClient"
:createTag="createTag"
:createTimeEntry="createTimeEntry"
v-model:show="showManualTimeEntryModal"
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:create-project="createProject"
:create-client="createClient"
:create-tag="createTag"
:create-time-entry="createTimeEntry"
:projects
:tasks
:tags
:clients
v-model:show="showManualTimeEntryModal"></TimeEntryCreateModal>
:clients></TimeEntryCreateModal>
<AppLayout title="Dashboard" data-testid="time_view">
<MainContainer
class="pt-5 lg:pt-8 pb-4 lg:pb-6 border-b border-default-background-separator">
@@ -141,8 +141,8 @@ function deleteSelected() {
<div class="pb-2 pt-2 lg:pt-0 lg:pl-4 flex justify-center">
<SecondaryButton
class="w-full text-center flex justify-center"
@click="showManualTimeEntryModal = true"
:icon="PlusIcon"
@click="showManualTimeEntryModal = true"
>Manual time entry
</SecondaryButton>
</div>
@@ -150,12 +150,9 @@ function deleteSelected() {
</MainContainer>
<TimeEntryMassActionRow
:selected-time-entries="selectedTimeEntries"
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:canCreateProject="canCreateProjects()"
@submit="clearSelectionAndState"
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()"
:all-selected="selectedTimeEntries.length === timeEntries.length"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"
:delete-selected="deleteSelected"
:projects="projects"
:tasks="tasks"
@@ -171,23 +168,26 @@ function deleteSelected() {
"
:create-project="createProject"
:create-client="createClient"
:createTag="createTag"></TimeEntryMassActionRow>
:create-tag="createTag"
@submit="clearSelectionAndState"
@select-all="selectedTimeEntries = [...timeEntries]"
@unselect-all="selectedTimeEntries = []"></TimeEntryMassActionRow>
<TimeEntryGroupedTable
v-model:selected="selectedTimeEntries"
:createProject
:enableEstimatedTime="isAllowedToPerformPremiumAction()"
:canCreateProject="canCreateProjects()"
:create-project
:enable-estimated-time="isAllowedToPerformPremiumAction()"
:can-create-project="canCreateProjects()"
:clients
:createClient
:updateTimeEntry
:updateTimeEntries
:deleteTimeEntries
:createTimeEntry="startTimeEntry"
:createTag
:create-client
:update-time-entry
:update-time-entries
:delete-time-entries
:create-time-entry="startTimeEntry"
:create-tag
:projects="projects"
:tasks="tasks"
:currency="getOrganizationCurrencyString()"
:timeEntries="timeEntries"
:time-entries="timeEntries"
:tags="tags"></TimeEntryGroupedTable>
<div v-if="timeEntries.length === 0" class="text-center pt-12">
<ClockIcon class="w-8 text-icon-default inline pb-2"></ClockIcon>

File diff suppressed because it is too large Load Diff

View File

@@ -57,13 +57,13 @@
"peerDependencies": {
"@floating-ui/vue": "^1.1.4",
"@heroicons/vue": "^2.1.5",
"@vueuse/core": "^10.11.0",
"@vueuse/core": "^12.5.0",
"@zodios/core": "^10.9.6",
"dayjs": "^1.11.13",
"parse-duration": "^1.1.0",
"parse-duration": "^2.0.1",
"tailwind-merge": "^2.5.2",
"tailwindcss": "^3.1.0",
"vue": "^3.4.38",
"vue-tsc": "^2.0.29"
"vue": "^3.5.0",
"vue-tsc": "^2.2.0"
}
}

View File

@@ -4,11 +4,11 @@ import { computed } from 'vue';
const props = withDefaults(
defineProps<{
size: 'base' | 'large' | 'xlarge';
tag: string;
size?: 'base' | 'large' | 'xlarge';
tag?: string;
class?: string;
color: string;
border: boolean;
color?: string;
border?: boolean;
}>(),
{
size: 'base',
@@ -30,6 +30,13 @@ const borderClasses = computed(() => {
}
return '';
});
const tagClasses = computed(() => {
if (props.tag === 'button') {
return 'hover:bg-tertiary';
}
return '';
});
</script>
<template>
@@ -37,9 +44,10 @@ const borderClasses = computed(() => {
:is="tag"
:class="
twMerge(
tagClasses,
badgeClasses[size],
borderClasses,
'rounded inline-flex items-center font-semibold text-white disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80',
'rounded transition inline-flex items-center font-semibold text-white disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
props.class
)
">

View File

@@ -6,9 +6,9 @@ import { twMerge } from 'tailwind-merge';
const props = withDefaults(
defineProps<{
type: HtmlButtonType;
type?: HtmlButtonType;
icon?: Component;
loading: boolean;
loading?: boolean;
}>(),
{
type: 'submit',
@@ -21,15 +21,15 @@ const props = withDefaults(
<button
:type="type"
:disabled="loading"
class="inline-flex items-center px-2 sm:px-3 py-1 sm:py-2 bg-accent-300/10 border border-accent-300/20 rounded-md font-medium text-xs sm:text-sm text-white hover:bg-accent-300/20 active:bg-accent-300/20 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
class="inline-flex items-center px-2 sm:px-3 py-1 sm:py-2 bg-accent-300/10 border border-accent-300/20 rounded-md font-medium text-xs sm:text-sm text-white hover:bg-accent-300/20 active:bg-accent-300/20 focus:outline-none focus-visible:ring-2 focus-visible:border-transparent focus-visible:ring-ring transition ease-in-out duration-150">
<span
:class="
twMerge('flex items-center ', props.icon ? 'space-x-1.5' : '')
">
<LoadingSpinner v-if="loading"></LoadingSpinner>
<component
v-if="props.icon && !loading"
:is="props.icon"
v-if="props.icon && !loading"
class="text-text-secondary w-4 -ml-0.5 mr-1"></component>
<span>
<slot />

View File

@@ -6,10 +6,10 @@ import LoadingSpinner from '../LoadingSpinner.vue';
const props = withDefaults(
defineProps<{
type: HtmlButtonType;
type?: HtmlButtonType;
icon?: Component;
size: 'small' | 'base';
loading: boolean;
size?: 'small' | 'base';
loading?: boolean;
class?: string;
}>(),
{
@@ -31,7 +31,7 @@ const sizeClasses = {
:disabled="loading"
:class="
twMerge(
'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus-visible:border-input-border-active focus:outline-none focus:ring-0 disabled:opacity-25 ease-in-out',
'bg-button-secondary-background border border-button-secondary-border hover:bg-button-secondary-background-hover shadow-sm transition text-white rounded-lg font-semibold inline-flex items-center space-x-1.5 focus-visible:outline-none focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring focus:border-transparent disabled:opacity-25 ease-in-out',
sizeClasses[props.size],
props.class
)
@@ -42,8 +42,8 @@ const sizeClasses = {
">
<LoadingSpinner v-if="loading"></LoadingSpinner>
<component
v-if="props.icon && !loading"
:is="props.icon"
v-if="props.icon && !loading"
class="text-text-tertiary w-4 -ml-0.5 mr-1"></component>
<span>
<slot />

View File

@@ -12,8 +12,8 @@ defineProps<{
<h3
class="text-white font-bold text-sm lg:text-base flex items-center space-x-2 lg:space-x-2.5">
<component
v-if="icon"
:is="icon"
v-if="icon"
class="w-5 lg:w-6 text-icon-default"></component>
<span>
{{ title }}

View File

@@ -127,28 +127,28 @@ const highlightedItem = computed(() => {
</script>
<template>
<Dropdown width="120" v-model="open" :closeOnContentClick="true">
<Dropdown v-model="open" width="120" :close-on-content-click="true">
<template #trigger>
<slot name="trigger"></slot>
</template>
<template #content>
<input
ref="searchInput"
:value="searchValue"
data-testid="client_dropdown_search"
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
placeholder="Search for a client..."
@input="updateSearchValue"
@keydown.enter="addClientIfNoneExists"
data-testid="client_dropdown_search"
@keydown.up.prevent="moveHighlightUp"
@keydown.down.prevent="moveHighlightDown"
ref="searchInput"
class="bg-card-background border-0 placeholder-muted text-sm text-white py-2.5 focus:ring-0 border-b border-card-background-separator focus:border-card-background-separator w-full"
placeholder="Search for a client..." />
@keydown.down.prevent="moveHighlightDown" />
<div ref="dropdownViewport" class="w-60 max-h-60 overflow-y-scroll">
<div
v-if="
searchValue.length > 0 && filteredClients.length === 0
"
@click="addClientIfNoneExists"
class="bg-card-background-active">
class="bg-card-background-active"
@click="addClientIfNoneExists">
<div
class="flex space-x-3 items-center px-4 py-3 text-xs text-white font-medium border-t rounded-b-lg border-card-background-separator">
<PlusCircleIcon
@@ -170,8 +170,8 @@ const highlightedItem = computed(() => {
:data-client-id="client.id">
<ClientDropdownItem
:selected="isClientSelected(client.id)"
@click="updateClient(client.id)"
:name="client.name"></ClientDropdownItem>
:name="client.name"
@click="updateClient(client.id)"></ClientDropdownItem>
</div>
</div>
</template>

View File

@@ -14,9 +14,9 @@ const emit = defineEmits(['submit']);
<InputLabel for="billable" value="Time Estimated" />
</div>
<DurationInput
@submit="emit('submit')"
v-model="model"
class="max-w-[150px]"></DurationInput>
class="max-w-[150px]"
@submit="emit('submit')"></DurationInput>
</div>
</template>

View File

@@ -5,7 +5,7 @@ import { computed } from 'vue';
const props = withDefaults(
defineProps<{
expanded?: boolean;
size: string;
size?: string;
}>(),
{
expanded: false,
@@ -17,7 +17,7 @@ const expandedStatusClasses = computed(() => {
if (props.expanded) {
return 'border-card-border border bg-card-background-active text-white';
}
return 'border-card-border border bg-card-background text-muted';
return 'border-card-border border bg-card-background hover:bg-card-background-active hover:text-text-primary transition text-muted';
});
</script>
@@ -25,7 +25,7 @@ const expandedStatusClasses = computed(() => {
<button
:class="
twMerge(
'font-medium rounded flex items-center transition justify-center',
'font-medium rounded flex items-center transition justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-transparent',
expandedStatusClasses,
props.size
)

View File

@@ -77,13 +77,13 @@ const inputValue = ref(formatValue(model.value));
:id="name"
ref="billableRateInput"
v-model="inputValue"
@blur="updateRate($event.target.value)"
@keydown.enter="updateRate($event.target.value)"
type="text"
:name="name"
placeholder="Billable Rate"
class="block w-full"
autocomplete="teamMemberRate" />
autocomplete="teamMemberRate"
@blur="updateRate($event.target.value)"
@keydown.enter="updateRate($event.target.value)" />
<div
class="absolute top-0 right-0 h-full flex items-center px-4 font-medium pointer-events-none">
<span>

View File

@@ -11,7 +11,7 @@ function toggleBillable() {
const props = withDefaults(
defineProps<{
size: 'small' | 'base';
size?: 'small' | 'base';
}>(),
{
size: 'base',
@@ -35,19 +35,19 @@ const iconSizeClasses = computed(() => {
});
const iconSizeWrapperClasses =
props.size === 'small' ? 'w-6 sm:w-8 h-6 sm:h-8' : 'w-11 h-11';
props.size === 'small' ? 'w-6 sm:w-8 h-6 sm:h-8' : 'w-10 h-10';
</script>
<template>
<button
@click="toggleBillable"
:class="
twMerge(
iconColorClasses,
iconSizeWrapperClasses,
'flex-shrink-0 ring-0 focus:outline-none focus:ring-0 transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center'
'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>
</template>

View File

@@ -31,9 +31,9 @@ const proxyChecked = computed({
<template>
<input
:id="id"
v-model="proxyChecked"
type="checkbox"
:id="id"
:value="value"
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:ring-accent-500/80" />
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:outline-none focus:ring-ring/50 focus-visible:outline-none focus-visible:ring-ring/50" />
</template>

View File

@@ -48,10 +48,8 @@ const emit = defineEmits(['changed']);
<template>
<div class="flex items-center text-muted">
<input
id="start"
ref="datePicker"
@change="updateTempValue"
@blur="updateDate"
@keydown.enter="updateDate"
:class="
twMerge(
'bg-input-background border text-white border-input-border focus-visible:outline-0 focus-visible:border-input-border-active focus-visible:ring-0 rounded-md',
@@ -59,9 +57,11 @@ const emit = defineEmits(['changed']);
)
"
type="date"
id="start"
name="trip-start"
:value="tempDate" />
:value="tempDate"
@change="updateTempValue"
@blur="updateDate"
@keydown.enter="updateDate" />
</div>
</template>

View File

@@ -4,7 +4,6 @@ import {
flip,
limitShift,
type Placement,
type ReferenceElement,
shift,
useFloating,
} from '@floating-ui/vue';
@@ -15,8 +14,8 @@ import { isLastLayer, layers } from '@/packages/ui/src/utils/dismissableLayer';
const props = withDefaults(
defineProps<{
align: Placement;
closeOnContentClick: boolean;
align?: Placement;
closeOnContentClick?: boolean;
}>(),
{
align: 'bottom-start',
@@ -45,6 +44,11 @@ watch(open, (value) => {
layers.value.push(id);
} else {
layers.value = layers.value.filter((layer) => layer !== id);
reference.value
?.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
?.focus();
}
});
@@ -69,7 +73,7 @@ function onBackgroundClick() {
open.value = false;
}
const reference = ref<null | ReferenceElement>(null);
const reference = ref<null | HTMLElement>(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
placement: props.align,
@@ -90,7 +94,7 @@ const { floatingStyles } = useFloating(reference, floating, {
<template>
<div class="min-w-0 isolate">
<div @click.prevent="toggleOpen" ref="reference" class="min-w-0">
<div ref="reference" class="min-w-0" @click.prevent="toggleOpen">
<slot name="trigger" />
</div>
@@ -109,8 +113,8 @@ const { floatingStyles } = useFloating(reference, floating, {
leave-to-class="transform opacity-0 scale-95">
<div
v-if="open"
class="z-50"
ref="floating"
class="z-50"
:style="floatingStyles"
@click="onContentClick">
<div

View File

@@ -78,11 +78,11 @@ function updateTimeEntryInputValue() {
<template>
<TextInput
ref="inputField"
@blur="updateDuration"
@keydown.enter="updateDuration"
v-model="temporaryCustomTimerEntry"
:class="twMerge('text-text-secondary', props.class)"
type="text" />
type="text"
@blur="updateDuration"
@keydown.enter="updateDuration" />
</template>
<style scoped></style>

View File

@@ -54,12 +54,12 @@ function updateAndSubmit() {
<template>
<div class="relative">
<TextInput
v-model="currentTime"
class="w-full overflow-hidden pr-14"
placeholder="0"
@focus="selectInput"
@blur="updateDuration"
@keydown.enter="updateAndSubmit"
v-model="currentTime">
@keydown.enter="updateAndSubmit">
</TextInput>
<div
class="absolute top-0 right-0 h-full flex items-center px-4 font-medium">

Some files were not shown because too many files have changed in this diff Show More