add dashboard frontend

This commit is contained in:
Gregor Vostrak
2024-03-11 18:02:54 +01:00
parent e8912650c0
commit 20fc123c36
86 changed files with 5124 additions and 849 deletions

View File

@@ -129,10 +129,9 @@ const deleteApiToken = () => {
createApiTokenForm.permissions
"
:value="permission" />
<span
class="ms-2 text-sm text-gray-600 dark:text-gray-400"
>{{ permission }}</span
>
<span class="ms-2 text-sm text-muted">{{
permission
}}</span>
</label>
</div>
</div>
@@ -246,10 +245,9 @@ const deleteApiToken = () => {
<Checkbox
v-model:checked="updateApiTokenForm.permissions"
:value="permission" />
<span
class="ms-2 text-sm text-gray-600 dark:text-gray-400"
>{{ permission }}</span
>
<span class="ms-2 text-sm text-muted">{{
permission
}}</span>
</label>
</div>
</div>

View File

@@ -33,7 +33,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<div class="mb-4 text-sm text-muted">
This is a secure area of the application. Please confirm your
password before continuing.
</div>

View File

@@ -28,7 +28,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<div class="mb-4 text-sm text-muted">
Forgot your password? No problem. Just let us know your email
address and we will email you a password reset link that will allow
you to choose a new one.

View File

@@ -73,9 +73,7 @@ const submit = () => {
<div class="block mt-4">
<label class="flex items-center">
<Checkbox v-model:checked="form.remember" name="remember" />
<span class="ms-2 text-sm text-gray-600 dark:text-gray-400"
>Remember me</span
>
<span class="ms-2 text-sm text-muted">Remember me</span>
</label>
</div>
@@ -83,7 +81,7 @@ const submit = () => {
<Link
v-if="canResetPassword"
:href="route('password.request')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
Forgot your password?
</Link>

View File

@@ -108,14 +108,14 @@ const page = usePage<{
<a
target="_blank"
:href="route('terms.show')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
>Terms of Service</a
>
and
<a
target="_blank"
:href="route('policy.show')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
>Privacy Policy</a
>
</div>
@@ -127,7 +127,7 @@ const page = usePage<{
<div class="flex items-center justify-end mt-4">
<Link
:href="route('login')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
Already registered?
</Link>

View File

@@ -45,7 +45,7 @@ const submit = () => {
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<div class="mb-4 text-sm text-muted">
<template v-if="!recovery">
Please confirm access to your account by entering the
authentication code provided by your authenticator application.
@@ -87,7 +87,7 @@ const submit = () => {
<div class="flex items-center justify-end mt-4">
<button
type="button"
class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 underline cursor-pointer"
class="text-sm text-muted hover:text-gray-900 underline cursor-pointer"
@click.prevent="toggleRecovery">
<template v-if="!recovery"> Use a recovery code</template>

View File

@@ -28,7 +28,7 @@ const verificationLinkSent = computed(
<AuthenticationCardLogo />
</template>
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<div class="mb-4 text-sm text-muted">
Before continuing, could you verify your email address by clicking
on the link we just emailed to you? If you didn't receive the email,
we will gladly send you another.
@@ -52,7 +52,7 @@ const verificationLinkSent = computed(
<div>
<Link
:href="route('profile.show')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800">
Edit Profile</Link
>
@@ -60,7 +60,7 @@ const verificationLinkSent = computed(
:href="route('logout')"
method="post"
as="button"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 ms-2">
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800 ms-2">
Log Out
</Link>
</div>

View File

@@ -1,24 +1,98 @@
<script setup lang="ts">
import AppLayout from '@/Layouts/AppLayout.vue';
import Welcome from '@/Components/Welcome.vue';
import TimeTracker from '@/Components/TimeTracker.vue';
import RecentlyTrackedTasksCard from '@/Components/Dashboard/RecentlyTrackedTasksCard.vue';
import LastSevenDaysCard from '@/Components/Dashboard/LastSevenDaysCard.vue';
import TeamActivityCard from '@/Components/Dashboard/TeamActivityCard.vue';
import ThisWeekOverview from '@/Components/Dashboard/ThisWeekOverview.vue';
import { usePage } from '@inertiajs/vue3';
import type { Organization, User } from '@/types/models';
import { onMounted } from 'vue';
import { useProjectsStore } from '@/utils/useProjects';
import ActivityGraphCard from '@/Components/Dashboard/ActivityGraphCard.vue';
const page = usePage<{
auth: {
user: User & {
all_teams: Organization[];
};
};
}>();
onMounted(async () => {
if (page.props.auth.user.current_team_id) {
await useProjectsStore().fetchProjects(
page.props.auth.user.current_team_id
);
}
});
const props = defineProps<{
latestTasks: {
id: string;
name: string;
project_name: string;
project_id: string;
}[];
latestTeamActivity: {
user_id: string;
name: string;
description: string;
time_entry_id: string;
task_id: string;
status: boolean;
}[];
lastSevenDays: {
date: string;
duration: number; // Total duration in seconds
history: number[]; // Array representing the duration in seconds of the 3h windows for the day
}[];
dailyTrackedHours: [string, number][];
weeklyProjectOverview: {
value: number;
name: string;
color: string;
}[];
totalWeeklyTime: number;
totalWeeklyBillableTime: number;
totalWeeklyBillableAmount: {
value: number;
currency: string;
};
weeklyHistory: {
date: string;
duration: number;
}[];
}>();
</script>
<template>
<AppLayout title="Dashboard">
<template #header>
<h2
class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
Dashboard
</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div
class="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg">
<Welcome />
</div>
</div>
<AppLayout title="Dashboard" data-testid="dashboard_view">
<div
class="py-8 sm:px-6 lg:px-8 mx-auto border-b border-default-background-seperator">
<TimeTracker></TimeTracker>
</div>
<div
class="grid gap-x-6 grid-cols-4 sm:px-6 lg:px-8 pt-6 pb-7 border-b border-default-background-seperator">
<RecentlyTrackedTasksCard
:latestTasks="props.latestTasks"></RecentlyTrackedTasksCard>
<LastSevenDaysCard
:last7-days="props.lastSevenDays"></LastSevenDaysCard>
<ActivityGraphCard
:daily-hours-tracked="
props.dailyTrackedHours
"></ActivityGraphCard>
<TeamActivityCard
:latestTeamActivity="
props.latestTeamActivity
"></TeamActivityCard>
</div>
<div class="sm:px-6 lg:px-8 py-6">
<ThisWeekOverview
:weeklyProjectOverview="props.weeklyProjectOverview"
:total-weekly-billable-amount="props.totalWeeklyBillableAmount"
:total-weekly-billable-time="props.totalWeeklyBillableTime"
:total-weekly-time="props.totalWeeklyTime"
:weekly-history="props.weeklyHistory"></ThisWeekOverview>
</div>
</AppLayout>
</template>

View File

@@ -44,7 +44,7 @@ const closeModal = () => {
<template #description> Permanently delete your account. </template>
<template #content>
<div class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="max-w-xl text-sm text-muted">
Once your account is deleted, all of its resources and data will
be permanently deleted. Before deleting your account, please
download any data or information that you wish to retain.

View File

@@ -53,7 +53,7 @@ const closeModal = () => {
</template>
<template #content>
<div class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="max-w-xl text-sm text-muted">
If necessary, you may log out of all of your other browser
sessions across all of your devices. Some of your recent
sessions are listed below; however, this list may not be
@@ -70,7 +70,7 @@ const closeModal = () => {
<div>
<svg
v-if="session.agent.is_desktop"
class="w-8 h-8 text-gray-500"
class="w-8 h-8 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
@@ -98,7 +98,7 @@ const closeModal = () => {
</div>
<div class="ms-3">
<div class="text-sm text-gray-600 dark:text-gray-400">
<div class="text-sm text-white font-medium">
{{
session.agent.platform
? session.agent.platform
@@ -113,7 +113,7 @@ const closeModal = () => {
</div>
<div>
<div class="text-xs text-gray-500">
<div class="text-xs text-muted">
{{ session.ip_address }},
<span

View File

@@ -137,13 +137,11 @@ const disableTwoFactorAuthentication = () => {
Finish enabling two factor authentication.
</h3>
<h3
v-else
class="text-lg font-medium text-gray-900 dark:text-gray-100">
<h3 v-else class="text-lg font-medium text-white">
You have not enabled two factor authentication.
</h3>
<div class="mt-3 max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="mt-3 max-w-xl text-sm text-muted">
<p>
When two factor authentication is enabled, you will be
prompted for a secure, random token during authentication.
@@ -154,8 +152,7 @@ const disableTwoFactorAuthentication = () => {
<div v-if="twoFactorEnabled">
<div v-if="qrCode">
<div
class="mt-4 max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="mt-4 max-w-xl text-sm text-muted">
<p v-if="confirming" class="font-semibold">
To finish enabling two factor authentication, scan
the following QR code using your phone's
@@ -176,7 +173,7 @@ const disableTwoFactorAuthentication = () => {
<div
v-if="setupKey"
class="mt-4 max-w-xl text-sm text-gray-600 dark:text-gray-400">
class="mt-4 max-w-xl text-sm text-muted">
<p class="font-semibold">
Setup Key: <span v-html="setupKey"></span>
</p>
@@ -203,8 +200,7 @@ const disableTwoFactorAuthentication = () => {
</div>
<div v-if="recoveryCodes.length > 0 && !confirming">
<div
class="mt-4 max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="mt-4 max-w-xl text-sm text-muted">
<p class="font-semibold">
Store these recovery codes in a secure password
manager. They can be used to recover access to your

View File

@@ -184,7 +184,7 @@ const page = usePage<{
:href="route('verification.send')"
method="post"
as="button"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
class="underline text-sm text-muted hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800"
@click.prevent="sendEmailVerification">
Click here to re-send the verification email.
</Link>

View File

@@ -31,7 +31,7 @@ const deleteTeam = () => {
<template #description> Permanently delete this team. </template>
<template #content>
<div class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="max-w-xl text-sm text-muted">
Once a team is deleted, all of its resources and data will be
permanently deleted. Before deleting this team, please download
any data or information regarding this team that you wish to

View File

@@ -138,8 +138,7 @@ const displayableRole = (role: string) => {
<template #form>
<div class="col-span-6">
<div
class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
<div class="max-w-xl text-sm text-muted">
Please provide the email address of the person you
would like to add to this team.
</div>
@@ -191,7 +190,7 @@ const displayableRole = (role: string) => {
<!-- Role Name -->
<div class="flex items-center">
<div
class="text-sm text-gray-600 dark:text-gray-400"
class="text-sm text-muted"
:class="{
'font-semibold':
addTeamMemberForm.role ==
@@ -220,7 +219,7 @@ const displayableRole = (role: string) => {
<!-- Role Description -->
<div
class="mt-2 text-xs text-gray-600 dark:text-gray-400 text-start">
class="mt-2 text-xs text-muted text-start">
{{ role.description }}
</div>
</div>
@@ -269,7 +268,7 @@ const displayableRole = (role: string) => {
v-for="invitation in team.team_invitations"
:key="invitation.id"
class="flex items-center justify-between">
<div class="text-gray-600 dark:text-gray-400">
<div class="text-muted">
{{ invitation.email }}
</div>
@@ -390,7 +389,7 @@ const displayableRole = (role: string) => {
<!-- Role Name -->
<div class="flex items-center">
<div
class="text-sm text-gray-600 dark:text-gray-400"
class="text-sm text-muted"
:class="{
'font-semibold':
updateRoleForm.role ===
@@ -415,8 +414,7 @@ const displayableRole = (role: string) => {
</div>
<!-- Role Description -->
<div
class="mt-2 text-xs text-gray-600 dark:text-gray-400">
<div class="mt-2 text-xs text-muted">
{{ role.description }}
</div>
</div>