add format check, update prettier rules, apply rules consistently

This commit is contained in:
Gregor Vostrak
2025-07-29 18:58:31 +02:00
parent b11672732b
commit a3d7cdaf4d
323 changed files with 3728 additions and 5579 deletions

View File

@@ -42,8 +42,8 @@ defineEmits<{
>.
</p>
<p class="py-1 text-center font-semibold max-w-md mx-auto">
Do you want to update all existing time entries, where the member
billable rate applies as well?
Do you want to update all existing time entries, where the member billable rate applies
as well?
</p>
</BillableRateModal>
</template>

View File

@@ -35,12 +35,8 @@ useFocus(searchInput, { initialValue: true });
const filteredMembers = computed<Member[]>(() => {
return members.value.filter((member) => {
return (
member.name
.toLowerCase()
.includes(searchValue.value?.toLowerCase()?.trim() || '') &&
!props.hiddenMembers.some(
(hiddenMember) => hiddenMember.member_id === member.id
) &&
member.name.toLowerCase().includes(searchValue.value?.toLowerCase()?.trim() || '') &&
!props.hiddenMembers.some((hiddenMember) => hiddenMember.member_id === member.id) &&
member.is_placeholder === false
);
});

View File

@@ -9,7 +9,7 @@ import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import Checkbox from '@/packages/ui/src/Input/Checkbox.vue';
import { useNotificationsStore } from '@/utils/notification';
import { getCurrentOrganizationId } from '@/utils/useUser';
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
import InputLabel from '@/packages/ui/src/Input/InputLabel.vue';
import InputError from '@/packages/ui/src/Input/InputError.vue';
import { useMembersStore } from '@/utils/useMembers';
@@ -30,7 +30,7 @@ const deleteMutation = useMutation({
if (!organizationId) {
throw new Error('No organization ID found');
}
return api.removeMember(undefined, {
params: {
member: props.member.id,
@@ -44,7 +44,7 @@ const deleteMutation = useMutation({
onSuccess: () => {
close();
useMembersStore().fetchMembers();
}
},
});
const form = useForm({
@@ -70,77 +70,77 @@ const close = () => {
<template>
<Modal :show="show" max-width="md" @close="close">
<div class="p-6">
<h2 class="text-lg font-medium text-text-primary">
Delete Member
</h2>
<h2 class="text-lg font-medium text-text-primary">Delete Member</h2>
<div class="mt-4 text-sm text-text-secondary">
<p class="mb-4">
Are you sure you want to delete {{ member.name }}? This action cannot be undone.
</p>
<p class="mb-4">
This will permanently delete:
</p>
<p class="mb-4">This will permanently delete:</p>
<ul class="list-disc ml-6 mt-2">
<li>All time entries created by this member</li>
<li>Their project assignments</li>
<li>Their organization membership</li>
</ul>
<ul class="list-disc ml-6 mt-2">
<li>All time entries created by this member</li>
<li>Their project assignments</li>
<li>Their organization membership</li>
</ul>
<p class="pt-4">
<strong>Note:</strong> Deleting time entries will affect all reports and statistics.
If you want to keep the time entries but remove the member from your organization, you can convert them to a placeholder user instead. Placeholder users are not charged and their time entries remain intact for reporting purposes.
<strong>Note:</strong> Deleting time entries will affect all reports and
statistics. If you want to keep the time entries but remove the member from your
organization, you can convert them to a placeholder user instead. Placeholder
users are not charged and their time entries remain intact for reporting
purposes.
</p>
</div>
<form
class="mt-6" @submit="
(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}
">
class="mt-6"
@submit="
(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}
">
<div class="flex items-start">
<form.Field
name="confirmDelete"
:validators="{
onSubmit: ({value}) => {
if (!value) {
return 'You must confirm that you understand the consequences of this action';
}
return '';
<form.Field
name="confirmDelete"
:validators="{
onSubmit: ({ value }) => {
if (!value) {
return 'You must confirm that you understand the consequences of this action';
}
}"
>
return '';
},
}">
<template #default="{ field }">
<div class="flex flex-col">
<div class="flex items-center space-x-3 text-sm">
<Checkbox
:id="field.name"
:name="field.name"
:checked="field.state.value"
@update:checked="field.handleChange"
@blur="field.handleBlur"
/>
<InputLabel :for="field.name" class="font-medium text-text-primary">
I understand that this will permanently delete all data related to this member
</InputLabel>
</div>
<InputError class="pl-7 pt-2" :message="field.state.meta.errors[0]" />
<div class="flex items-center space-x-3 text-sm">
<Checkbox
:id="field.name"
:name="field.name"
:checked="field.state.value"
@update:checked="field.handleChange"
@blur="field.handleBlur" />
<InputLabel
:for="field.name"
class="font-medium text-text-primary">
I understand that this will permanently delete all data
related to this member
</InputLabel>
</div>
<InputError
class="pl-7 pt-2"
:message="field.state.meta.errors[0]" />
</div>
</template>
</form.Field>
</form.Field>
</div>
<div class="mt-6 flex justify-end space-x-3">
<SecondaryButton @click="close">Cancel</SecondaryButton>
<form.Subscribe>
<template #default="{ canSubmit, isSubmitting }">
<DangerButton
type="submit"
:disabled="!canSubmit"
>
{{ isSubmitting ? 'Deleting...' : 'Delete Member' }}
<DangerButton type="submit" :disabled="!canSubmit">
{{ isSubmitting ? 'Deleting...' : 'Delete Member' }}
</DangerButton>
</template>
</form.Subscribe>
@@ -148,4 +148,4 @@ class="mt-6" @submit="
</form>
</div>
</Modal>
</template>
</template>

View File

@@ -54,10 +54,7 @@ function saveWithChecks() {
showBillableRateModal.value = true;
}, 0);
show.value = false;
} else if (
memberBody.value.role === 'owner' &&
props.member.role !== 'owner'
) {
} else if (memberBody.value.role === 'owner' && props.member.role !== 'owner') {
show.value = false;
showOwnershipTransferConfirmModal.value = true;
} else {
@@ -96,10 +93,7 @@ const roleDescriptionTexts = {
};
const roleDescription = computed(() => {
if (
memberBody.value.role &&
memberBody.value.role in roleDescriptionTexts
) {
if (memberBody.value.role && memberBody.value.role in roleDescriptionTexts) {
return roleDescriptionTexts[memberBody.value.role];
}
return '';
@@ -143,22 +137,14 @@ const roleDescription = computed(() => {
<div>
<InputLabel for="billableType" value="Billable" />
<MemberBillableSelect
v-model="
billableRateSelect
"
v-model="billableRateSelect"
class="mt-2"
name="billableType"></MemberBillableSelect>
</div>
<div
v-if="billableRateSelect === 'custom-rate'"
class="flex-1">
<InputLabel
for="memberBillableRate"
value="Billable Rate" />
<div v-if="billableRateSelect === 'custom-rate'" class="flex-1">
<InputLabel for="memberBillableRate" value="Billable Rate" />
<BillableRateInput
v-model="
memberBody.billable_rate
"
v-model="memberBody.billable_rate"
focus
class="w-full"
:currency="getOrganizationCurrencyString()"

View File

@@ -11,10 +11,7 @@ import type { Role } from '@/types/jetstream';
import { Link, useForm } from '@inertiajs/vue3';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { filterRoles } from '@/utils/roles';
import {
isAllowedToPerformPremiumAction,
isBillingActivated,
} from '@/utils/billing';
import { isAllowedToPerformPremiumAction, isBillingActivated } from '@/utils/billing';
import { CreditCardIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
import { canManageBilling, canUpdateOrganization } from '@/utils/permissions';
import { api } from '@/packages/api/src';
@@ -44,14 +41,10 @@ const { handleApiRequestNotifications } = useNotificationsStore();
async function submit() {
if (addTeamMemberForm.role === null || addTeamMemberForm.email === '') {
errors.value.email = z
.string()
.email()
.safeParse(addTeamMemberForm.email).success
errors.value.email = z.string().email().safeParse(addTeamMemberForm.email).success
? ''
: 'Please enter a valid email address';
errors.value.role =
addTeamMemberForm.role === null ? 'Please select a role' : '';
errors.value.role = addTeamMemberForm.role === null ? 'Please select a role' : '';
return;
}
@@ -100,21 +93,15 @@ useFocus(clientNameInput, { initialValue: true });
<UserGroupIcon class="w-12"></UserGroupIcon>
</div>
<div class="max-w-sm text-center mx-auto py-4 text-base">
<p class="py-1">
The Free plan is <strong>limited to one member</strong>
</p>
<p class="py-1">The Free plan is <strong>limited to one member</strong></p>
<p class="py-1">
To add new team members to your organization you,
<strong>please upgrade to a paid plan</strong>.
</p>
<Link
v-if="isBillingActivated() && canManageBilling()"
href="/billing">
<Link v-if="isBillingActivated() && canManageBilling()" href="/billing">
<PrimaryButton
v-if="
isBillingActivated() && canUpdateOrganization()
"
v-if="isBillingActivated() && canUpdateOrganization()"
type="button"
class="mt-6">
<CreditCardIcon class="w-5 h-5 me-2" />
@@ -154,8 +141,7 @@ useFocus(clientNameInput, { initialValue: true });
:class="{
'border-t border-card-border focus:border-none rounded-t-none':
i > 0,
'rounded-b-none':
i != Object.keys(availableRoles).length - 1,
'rounded-b-none': i != Object.keys(availableRoles).length - 1,
}"
@click="addTeamMemberForm.role = role.key">
<div
@@ -169,17 +155,13 @@ useFocus(clientNameInput, { initialValue: true });
<div
class="text-sm text-text-primary"
:class="{
'font-semibold':
addTeamMemberForm.role ==
role.key,
'font-semibold': addTeamMemberForm.role == role.key,
}">
{{ role.name }}
</div>
<svg
v-if="
addTeamMemberForm.role == role.key
"
v-if="addTeamMemberForm.role == role.key"
class="ms-2 h-5 w-5 text-green-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import DialogModal from '@/packages/ui/src/DialogModal.vue';
import {ref} from 'vue';
import {api, type Member} from '@/packages/api/src';
import { ref } from 'vue';
import { api, type Member } from '@/packages/api/src';
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
import {useMutation} from '@tanstack/vue-query';
import {getCurrentOrganizationId} from "@/utils/useUser";
import {useNotificationsStore} from "@/utils/notification";
import {useMembersStore} from "@/utils/useMembers";
import { useMutation } from '@tanstack/vue-query';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { useMembersStore } from '@/utils/useMembers';
const {handleApiRequestNotifications} = useNotificationsStore();
const { handleApiRequestNotifications } = useNotificationsStore();
const show = defineModel('show', {default: false});
const show = defineModel('show', { default: false });
const saving = ref(false);
const props = defineProps<{
@@ -27,7 +27,7 @@ const turnToPlaceholderMutation = useMutation({
return await api.makePlaceholder(undefined, {
params: {
organization: organizationId,
member: props.member.id
member: props.member.id,
},
});
},
@@ -36,17 +36,15 @@ const turnToPlaceholderMutation = useMutation({
async function submit() {
saving.value = true;
await handleApiRequestNotifications(
() =>
turnToPlaceholderMutation.mutateAsync(),
() => turnToPlaceholderMutation.mutateAsync(),
'Deactivating the member was successful!',
'There was an error deactivating the user.',
() => {
show.value = false;
useMembersStore().fetchMembers()
useMembersStore().fetchMembers();
}
);
}
</script>
<template>
@@ -59,8 +57,9 @@ async function submit() {
<template #content>
<p>
Deactivating the user <strong>{{ member.name }} </strong> will remove the user's access to
the organization. You will not be billed for inactive users and all time entries will be preserved.
Deactivating the user <strong>{{ member.name }} </strong> will remove the user's
access to the organization. You will not be billed for inactive users and all time
entries will be preserved.
</p>
</template>
<template #footer>

View File

@@ -2,14 +2,14 @@
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import DialogModal from '@/packages/ui/src/DialogModal.vue';
import { ref } from 'vue';
import {api, type Member} from '@/packages/api/src';
import { api, type Member } from '@/packages/api/src';
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
import MemberCombobox from "@/Components/Common/Member/MemberCombobox.vue";
import {UserIcon, ArrowRightIcon} from "@heroicons/vue/24/solid";
import {Badge} from "@/packages/ui/src";
import MemberCombobox from '@/Components/Common/Member/MemberCombobox.vue';
import { UserIcon, ArrowRightIcon } from '@heroicons/vue/24/solid';
import { Badge } from '@/packages/ui/src';
import { useMutation } from '@tanstack/vue-query';
import {getCurrentOrganizationId} from "@/utils/useUser";
import {useNotificationsStore} from "@/utils/notification";
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
const { handleApiRequestNotifications, addNotification } = useNotificationsStore();
const show = defineModel('show', { default: false });
@@ -27,40 +27,36 @@ const mergeMember = useMutation({
if (organizationId === null) {
throw new Error('No current organization id - create report');
}
return await api.mergeMember({
member_id: newMemberId,
}, {
params: {
organization: organizationId,
member: props.member.id
return await api.mergeMember(
{
member_id: newMemberId,
},
});
{
params: {
organization: organizationId,
member: props.member.id,
},
}
);
},
});
async function submit() {
const newMemberId = newMember.value;
if(newMemberId !== ''){
if (newMemberId !== '') {
saving.value = true;
await handleApiRequestNotifications(
() =>
mergeMember.mutateAsync(newMemberId),
() => mergeMember.mutateAsync(newMemberId),
'Members successfully merged!',
'There was an error merging the members.',
() => {
show.value = false;
}
);
} else {
addNotification('error', 'Please select a member to merge into.');
}
else{
addNotification(
'error',
'Please select a member to merge into.',
);
}
}
</script>
<template>
@@ -72,10 +68,14 @@ async function submit() {
</template>
<template #content>
<p>Merging the user <strong>{{ member.name }} </strong> into another one will transfer all time entries to the new user. <strong>This cannot be reverted!</strong></p>
<p>
Merging the user <strong>{{ member.name }} </strong> into another one will transfer
all time entries to the new user. <strong>This cannot be reverted!</strong>
</p>
<div class="py-5 flex flex-col md:flex-row gap-6 items-center">
<div class="flex-1">
<Badge class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
<Badge
class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
<UserIcon class="relative z-10 w-4 text-text-secondary"></UserIcon>
<div class="flex-1 font-medium truncate">
{{ member.name }}
@@ -86,9 +86,7 @@ async function submit() {
<ArrowRightIcon class="relative z-10 w-4 text-muted"></ArrowRightIcon>
</div>
<div class="flex-1">
<MemberCombobox
v-model="newMember"
></MemberCombobox>
<MemberCombobox v-model="newMember"></MemberCombobox>
</div>
</div>
</template>

View File

@@ -1,7 +1,17 @@
<script setup lang="ts">
import { TrashIcon, UserCircleIcon, PencilSquareIcon, ArrowDownOnSquareStackIcon } from '@heroicons/vue/20/solid';
import {
TrashIcon,
UserCircleIcon,
PencilSquareIcon,
ArrowDownOnSquareStackIcon,
} from '@heroicons/vue/20/solid';
import type { Member } from '@/packages/api/src';
import {canDeleteMembers, canMakeMembersPlaceholders, canMergeMembers, canUpdateMembers} from '@/utils/permissions';
import {
canDeleteMembers,
canMakeMembersPlaceholders,
canMergeMembers,
canUpdateMembers,
} from '@/utils/permissions';
import {
DropdownMenu,
DropdownMenuContent,

View File

@@ -26,8 +26,8 @@ const emit = defineEmits<{
<div class="flex items-center space-x-4">
<div class="col-span-6 sm:col-span-4 flex-1">
<p class="py-1 text-center">
You are about to transfer the ownership of this
organization to {{ memberName }}.
You are about to transfer the ownership of this organization to
{{ memberName }}.
</p>
</div>
</div>

View File

@@ -22,9 +22,7 @@ function getNameFromItem(item: Role) {
}
function getNameForKey(key: string | undefined) {
const item = page.props.availableRoles.find(
(item) => getKeyFromItem(item) === key
);
const item = page.props.availableRoles.find((item) => getKeyFromItem(item) === key);
if (item) {
return getNameFromItem(item);
}

View File

@@ -10,12 +10,9 @@ import TableHeading from '@/Components/Common/TableHeading.vue';
</div>
<div class="px-3 py-1.5 text-left font-semibold text-text-primary">Email</div>
<div class="px-3 py-1.5 text-left font-semibold text-text-primary">Role</div>
<div class="px-3 py-1.5 text-left font-semibold text-text-primary">
Billable Rate
</div>
<div class="px-3 py-1.5 text-left font-semibold text-text-primary">Billable Rate</div>
<div class="px-3 py-1.5 text-left font-semibold text-text-primary">Status</div>
<div
class="relative py-1.5 pl-3 pr-4 sm:pr-6 lg:pr-8 3xl:pr-12 bg-row-heading-background">
<div class="relative py-1.5 pl-3 pr-4 sm:pr-6 lg:pr-8 3xl:pr-12 bg-row-heading-background">
<span class="sr-only">Edit</span>
</div>
</TableHeading>

View File

@@ -86,13 +86,9 @@ const userHasValidMailAddress = computed(() => {
</div>
<div
class="whitespace-nowrap px-3 py-4 text-sm text-text-secondary flex space-x-1 items-center font-medium">
<CheckCircleIcon
v-if="member.is_placeholder === false"
class="w-5"></CheckCircleIcon>
<CheckCircleIcon v-if="member.is_placeholder === false" class="w-5"></CheckCircleIcon>
<span v-if="member.is_placeholder === false">Active</span>
<UserCircleIcon
v-if="member.is_placeholder === true"
class="w-5"></UserCircleIcon>
<UserCircleIcon v-if="member.is_placeholder === true" class="w-5"></UserCircleIcon>
<span v-if="member.is_placeholder === true">Inactive</span>
</div>
<div
@@ -116,12 +112,8 @@ const userHasValidMailAddress = computed(() => {
showMakeMemberPlaceholderModal = true
"></MemberMoreOptionsDropdown>
</div>
<MemberEditModal
v-model:show="showEditMemberModal"
:member="member"></MemberEditModal>
<MemberMergeModal
v-model:show="showMergeMemberModal"
:member="member"></MemberMergeModal>
<MemberEditModal v-model:show="showEditMemberModal" :member="member"></MemberEditModal>
<MemberMergeModal v-model:show="showMergeMemberModal" :member="member"></MemberMergeModal>
<MemberMakePlaceholderModal
v-model:show="showMakeMemberPlaceholderModal"
:member="member"></MemberMakePlaceholderModal>