mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-05-07 20:32:26 +00:00
add Progress component and Reorganize UI exports
This commit is contained in:
@@ -603,7 +603,124 @@ export async function getInvitationsViaApi(ctx: TestContext) {
|
||||
const response = await ctx.request.get(
|
||||
`${PLAYWRIGHT_BASE_URL}/api/v1/organizations/${ctx.orgId}/invitations`
|
||||
);
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
const body = await response.json();
|
||||
return body.data as Array<{ id: string; email: string; role: string }>;
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Timestamp-based time entry helpers
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export async function createTimeEntryWithTimestampsViaApi(
|
||||
ctx: TestContext,
|
||||
data: {
|
||||
description?: string;
|
||||
start: string;
|
||||
end: string;
|
||||
projectId?: string | null;
|
||||
taskId?: string | null;
|
||||
tags?: string[];
|
||||
billable?: boolean;
|
||||
}
|
||||
) {
|
||||
const response = await ctx.request.post(
|
||||
`${PLAYWRIGHT_BASE_URL}/api/v1/organizations/${ctx.orgId}/time-entries`,
|
||||
{
|
||||
data: {
|
||||
member_id: ctx.memberId,
|
||||
start: data.start,
|
||||
end: data.end,
|
||||
description: data.description ?? '',
|
||||
project_id: data.projectId ?? null,
|
||||
task_id: data.taskId ?? null,
|
||||
tags: data.tags ?? [],
|
||||
billable: data.billable ?? false,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status()).toBe(201);
|
||||
const body = await response.json();
|
||||
return body.data as { id: string; start: string; end: string; description: string };
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// User profile helpers
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export async function updateUserProfileViaWeb(
|
||||
page: Page,
|
||||
settings: { timezone?: string; week_start?: string }
|
||||
) {
|
||||
// Read user info from Inertia's data-page attribute on the root element
|
||||
const userInfo = await page.evaluate(() => {
|
||||
// Try Inertia's data-page attribute (stores initial page props as JSON)
|
||||
const appEl = document.getElementById('app');
|
||||
if (appEl) {
|
||||
const dataPage = appEl.getAttribute('data-page');
|
||||
if (dataPage) {
|
||||
try {
|
||||
const parsed = JSON.parse(dataPage);
|
||||
const user = parsed?.props?.auth?.user;
|
||||
if (user) {
|
||||
return {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
timezone: user.timezone,
|
||||
week_start: user.week_start,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// JSON parse failed
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (!userInfo) throw new Error('Could not read user info from Inertia data-page attribute');
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
|
||||
const xsrfToken = xsrfCookie ? decodeURIComponent(xsrfCookie.value) : '';
|
||||
|
||||
const response = await page.request.put(`${PLAYWRIGHT_BASE_URL}/user/profile-information`, {
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': xsrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
data: {
|
||||
name: userInfo.name,
|
||||
email: userInfo.email,
|
||||
timezone: settings.timezone ?? userInfo.timezone,
|
||||
week_start: settings.week_start ?? userInfo.week_start,
|
||||
},
|
||||
});
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────
|
||||
// Running time entry with specific start
|
||||
// ──────────────────────────────────────────────────
|
||||
|
||||
export async function createRunningTimeEntryWithStartViaApi(
|
||||
ctx: TestContext,
|
||||
description: string,
|
||||
start: string
|
||||
) {
|
||||
const response = await ctx.request.post(
|
||||
`${PLAYWRIGHT_BASE_URL}/api/v1/organizations/${ctx.orgId}/time-entries`,
|
||||
{
|
||||
data: {
|
||||
member_id: ctx.memberId,
|
||||
start,
|
||||
description,
|
||||
billable: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(response.status()).toBe(201);
|
||||
const body = await response.json();
|
||||
return body.data as { id: string; start: string; end: null; description: string };
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ function closeAndFocusInput() {
|
||||
v-model="currentTime"
|
||||
placeholder="00:00:00"
|
||||
data-testid="time_entry_time"
|
||||
class="w-[110px] lg:w-[120px] h-full text-text-primary py-2.5 rounded-lg border-border-secondary border text-center px-4 text-base font-semibold tabular-nums bg-card-background border-none placeholder-text-tertiary focus:ring-0 transition"
|
||||
class="w-[110px] lg:w-[120px] h-full text-text-primary py-2.5 rounded-lg border-border-secondary border text-center px-4 text-base font-semibold bg-card-background border-none placeholder-text-tertiary focus:ring-0 transition"
|
||||
type="text"
|
||||
@focusin="openModalOnTab"
|
||||
@click="openModalOnClick"
|
||||
|
||||
@@ -5,46 +5,58 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
import * as money from './utils/money';
|
||||
import * as color from './utils/color';
|
||||
import * as money from './utils/money';
|
||||
import * as random from './utils/random';
|
||||
import * as time from './utils/time';
|
||||
|
||||
export { cn } from './utils/cn';
|
||||
export { buttonVariants, type ButtonVariants } from './Buttons/index';
|
||||
export type {
|
||||
CommandPaletteCommand,
|
||||
CommandPaletteGroup,
|
||||
EntitySearchResult,
|
||||
} from './CommandPalette/index';
|
||||
export type { FieldVariants } from './field/index';
|
||||
export type { CalendarSettings } from './FullCalendar/calendarSettings';
|
||||
export type { ActivityPeriod } from './FullCalendar/activityTypes';
|
||||
export { cn } from './utils/cn';
|
||||
|
||||
import Badge from './Badge.vue';
|
||||
import Button from './Buttons/Button.vue';
|
||||
import PrimaryButton from './Buttons/PrimaryButton.vue';
|
||||
import SecondaryButton from './Buttons/SecondaryButton.vue';
|
||||
import Button from './Buttons/Button.vue';
|
||||
import TimeTrackerStartStop from './TimeTrackerStartStop.vue';
|
||||
import ProjectBadge from './Project/ProjectBadge.vue';
|
||||
import CardTitle from './CardTitle.vue';
|
||||
import Checkbox from './Input/Checkbox.vue';
|
||||
import InputLabel from './Input/InputLabel.vue';
|
||||
import TextInput from './Input/TextInput.vue';
|
||||
import LoadingSpinner from './LoadingSpinner.vue';
|
||||
import Modal from './Modal.vue';
|
||||
import TextInput from './Input/TextInput.vue';
|
||||
import InputLabel from './Input/InputLabel.vue';
|
||||
import TimeTrackerRunningInDifferentOrganizationOverlay from './TimeTracker/TimeTrackerRunningInDifferentOrganizationOverlay.vue';
|
||||
import TimeTrackerControls from './TimeTracker/TimeTrackerControls.vue';
|
||||
import TimeTrackerMoreOptionsDropdown from './TimeTracker/TimeTrackerMoreOptionsDropdown.vue';
|
||||
import CardTitle from './CardTitle.vue';
|
||||
import Badge from './Badge.vue';
|
||||
import Checkbox from './Input/Checkbox.vue';
|
||||
import TimeEntryGroupedTable from './TimeEntry/TimeEntryGroupedTable.vue';
|
||||
import TimeEntryMassActionRow from './TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import ProjectBadge from './Project/ProjectBadge.vue';
|
||||
import TimeEntryCreateModal from './TimeEntry/TimeEntryCreateModal.vue';
|
||||
import TimeEntryEditModal from './TimeEntry/TimeEntryEditModal.vue';
|
||||
import TimeEntryGroupedTable from './TimeEntry/TimeEntryGroupedTable.vue';
|
||||
import TimeEntryMassActionRow from './TimeEntry/TimeEntryMassActionRow.vue';
|
||||
import TimeTrackerControls from './TimeTracker/TimeTrackerControls.vue';
|
||||
import TimeTrackerMoreOptionsDropdown from './TimeTracker/TimeTrackerMoreOptionsDropdown.vue';
|
||||
import TimeTrackerRunningInDifferentOrganizationOverlay from './TimeTracker/TimeTrackerRunningInDifferentOrganizationOverlay.vue';
|
||||
import TimeTrackerStartStop from './TimeTrackerStartStop.vue';
|
||||
|
||||
import FullCalendarEventContent from './FullCalendar/FullCalendarEventContent.vue';
|
||||
import FullCalendarDayHeader from './FullCalendar/FullCalendarDayHeader.vue';
|
||||
import TimeEntryCalendar from './FullCalendar/TimeEntryCalendar.vue';
|
||||
import CalendarSettingsPopover from './FullCalendar/CalendarSettingsPopover.vue';
|
||||
import DateRangePicker from './Input/DateRangePicker.vue';
|
||||
import TimezoneMismatchModal from './TimezoneMismatchModal.vue';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip/index';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './accordion/index';
|
||||
import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './popover/index';
|
||||
import { RangeCalendar } from './range-calendar/index';
|
||||
import {
|
||||
Calendar,
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton,
|
||||
} from './calendar/index';
|
||||
import { CommandPalette } from './CommandPalette/index';
|
||||
import { Separator } from './separator/index';
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
@@ -61,6 +73,17 @@ import {
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from './context-menu/index';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogScrollContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from './dialog/index';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@@ -78,52 +101,6 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from './dropdown-menu/index';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from './select/index';
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldContent,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
} from './number-field/index';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogScrollContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from './dialog/index';
|
||||
import {
|
||||
Calendar,
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton,
|
||||
} from './calendar/index';
|
||||
import { Label } from './label/index';
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
@@ -137,71 +114,65 @@ import {
|
||||
FieldTitle,
|
||||
fieldVariants,
|
||||
} from './field/index';
|
||||
export type { FieldVariants } from './field/index';
|
||||
export type { ActivityPeriod } from './FullCalendar/idleStatusPlugin';
|
||||
export type { CalendarSettings } from './FullCalendar/calendarSettings';
|
||||
export type {
|
||||
CommandPaletteCommand,
|
||||
CommandPaletteGroup,
|
||||
EntitySearchResult,
|
||||
} from './CommandPalette/index';
|
||||
import CalendarSettingsPopover from './FullCalendar/CalendarSettingsPopover.vue';
|
||||
import CalendarToolbar from './FullCalendar/CalendarToolbar.vue';
|
||||
import FullCalendarDayHeader from './FullCalendar/FullCalendarDayHeader.vue';
|
||||
import FullCalendarEventContent from './FullCalendar/FullCalendarEventContent.vue';
|
||||
import TimeEntryCalendar from './FullCalendar/TimeEntryCalendar.vue';
|
||||
import DateRangePicker from './Input/DateRangePicker.vue';
|
||||
import { Label } from './label/index';
|
||||
import {
|
||||
NumberField,
|
||||
NumberFieldContent,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
} from './number-field/index';
|
||||
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from './popover/index';
|
||||
import { Progress } from './progress/index';
|
||||
import { RangeCalendar } from './range-calendar/index';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectItemText,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from './select/index';
|
||||
import { Separator } from './separator/index';
|
||||
import TimezoneMismatchModal from './TimezoneMismatchModal.vue';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip/index';
|
||||
|
||||
export {
|
||||
money,
|
||||
color,
|
||||
random,
|
||||
time,
|
||||
Button,
|
||||
PrimaryButton,
|
||||
SecondaryButton,
|
||||
TimeTrackerStartStop,
|
||||
ProjectBadge,
|
||||
LoadingSpinner,
|
||||
Modal,
|
||||
TextInput,
|
||||
InputLabel,
|
||||
TimeTrackerRunningInDifferentOrganizationOverlay,
|
||||
TimeTrackerControls,
|
||||
TimeTrackerMoreOptionsDropdown,
|
||||
CardTitle,
|
||||
Badge,
|
||||
Checkbox,
|
||||
TimeEntryGroupedTable,
|
||||
TimeEntryMassActionRow,
|
||||
TimeEntryCreateModal,
|
||||
TimeEntryEditModal,
|
||||
FullCalendarEventContent,
|
||||
FullCalendarDayHeader,
|
||||
TimeEntryCalendar,
|
||||
CalendarSettingsPopover,
|
||||
DateRangePicker,
|
||||
TimezoneMismatchModal,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
PopoverAnchor,
|
||||
RangeCalendar,
|
||||
Badge,
|
||||
Button,
|
||||
Calendar,
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton,
|
||||
CalendarSettingsPopover,
|
||||
CalendarToolbar,
|
||||
CardTitle,
|
||||
Checkbox,
|
||||
color,
|
||||
CommandPalette,
|
||||
Separator,
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
fieldVariants,
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
@@ -216,6 +187,16 @@ export {
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
DateRangePicker,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogScrollContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
@@ -231,6 +212,39 @@ export {
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
fieldVariants,
|
||||
FullCalendarDayHeader,
|
||||
FullCalendarEventContent,
|
||||
InputLabel,
|
||||
Label,
|
||||
LoadingSpinner,
|
||||
Modal,
|
||||
money,
|
||||
NumberField,
|
||||
NumberFieldContent,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
PrimaryButton,
|
||||
Progress,
|
||||
ProjectBadge,
|
||||
random,
|
||||
RangeCalendar,
|
||||
SecondaryButton,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
@@ -242,31 +256,21 @@ export {
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
NumberField,
|
||||
NumberFieldContent,
|
||||
NumberFieldDecrement,
|
||||
NumberFieldIncrement,
|
||||
NumberFieldInput,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogScrollContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Calendar,
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNextButton,
|
||||
CalendarPrevButton,
|
||||
Label,
|
||||
Separator,
|
||||
TextInput,
|
||||
time,
|
||||
TimeEntryCalendar,
|
||||
TimeEntryCreateModal,
|
||||
TimeEntryEditModal,
|
||||
TimeEntryGroupedTable,
|
||||
TimeEntryMassActionRow,
|
||||
TimeTrackerControls,
|
||||
TimeTrackerMoreOptionsDropdown,
|
||||
TimeTrackerRunningInDifferentOrganizationOverlay,
|
||||
TimeTrackerStartStop,
|
||||
TimezoneMismatchModal,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '../utils/cn';
|
||||
import { ProgressIndicator, ProgressRoot, type ProgressRootProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = withDefaults(defineProps<ProgressRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
modelValue: 0,
|
||||
});
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProgressRoot
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', props.class)">
|
||||
<ProgressIndicator
|
||||
class="h-full w-full flex-1 bg-quaternary transition-all"
|
||||
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%)`" />
|
||||
</ProgressRoot>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Progress } from './Progress.vue';
|
||||
Reference in New Issue
Block a user