mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 12:42:15 +01:00
add dashboard frontend
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user