Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display goals on home page #308

Merged
merged 3 commits into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions backend/app/api/v1/routes/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,11 @@ async def delete_goal_by_name(goal_name: str, current_user: CurrentUser) -> None


@router.get("/")
async def get_user_goals(current_user: CurrentUser) -> list[Goal]:
async def get_user_goals(current_user: CurrentUser) -> list[Goal] | None:
"""Get goals for a user."""
logger.info("Getting goals for user %s", current_user.id)
goals = await get_goals_by_user_id(ObjectId(current_user.id))

if not goals:
logger.info("No goals found for user %s", current_user.id)
raise HTTPException(
status_code=HTTP_404_NOT_FOUND, detail=f"No goals found for user {current_user.id}"
)

return goals


Expand Down
8 changes: 8 additions & 0 deletions backend/app/models/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from enum import Enum

from beanie import Document
from pydantic import BaseModel, Field, field_validator
Expand All @@ -7,6 +8,11 @@
from app.models.object_id import ObjectIdStr


class GoalStatus(str, Enum):
ACTIVE = "active"
COMPLTED = "completed"


class DaysOfWeek(BaseModel):
monday: bool = False
tuesday: bool = False
Expand All @@ -28,6 +34,7 @@ class _GoalBase(BaseModel):
days_of_week: DaysOfWeek | None = None
time_of_day: str | None = None
progress: float | None = None
status: GoalStatus = GoalStatus.ACTIVE

@field_validator("time_of_day")
@classmethod
Expand Down Expand Up @@ -72,6 +79,7 @@ class Settings:
"days_of_week": "$days_of_week",
"time_of_day": "$time_of_day",
"progress": "$prograss",
"status": "$status",
}


Expand Down
4 changes: 3 additions & 1 deletion backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from app.core.security import get_password_hash
from app.db import init_db
from app.main import app
from app.models.user import User
from app.models.user import GoalStatus, User


@pytest.fixture(scope="session", autouse=True)
Expand Down Expand Up @@ -67,6 +67,7 @@ def user_data():
"days_of_week": None,
"time_of_day": None,
"progress": 41.0,
"status": GoalStatus.ACTIVE,
},
{
"id": str(uuid4()),
Expand All @@ -88,6 +89,7 @@ def user_data():
},
"time_of_day": "14:41",
"progress": 42.0,
"status": GoalStatus.ACTIVE,
},
],
}
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_goal_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ async def test_get_all_goals(test_client, user_with_goals, user_token_headers):
@pytest.mark.usefixtures("user_no_goals")
async def test_get_all_goals_no_goals(test_client, user_token_headers):
response = await test_client.get("goal/", headers=user_token_headers)
assert response.status_code == 404
assert response.json() is None


async def test_get_all_goals_user_not_authenticated(test_client):
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function authHeaders(): Promise<AxiosRequestConfig<any>> {
throw new Error('No access token found');
}

export const createGoal = async (payload: GoalCreate): Promise<GoalOutput> => {
export const createGoal = async (payload: GoalCreate): Promise<GoalOutput[]> => {
const headers = await authHeaders();
const response = await axiosInstance.post('/goal', payload, headers);

Expand Down Expand Up @@ -73,6 +73,18 @@ export const forgotPassword = async (payload: PasswordReset): Promise<UserNoPass
}
};

export const getGoals = async (): Promise<GoalOutput[] | null> => {
// TODO: Better handle errors
const headers = await authHeaders();
const response = await axiosInstance.get('/goal', headers);

if (response.status === 200) {
return response.data;
} else {
throw new Error(response.statusText);
}
};

export const getMe = async (): Promise<UserNoPassword> => {
// TODO: Better handle errors
const headers = await authHeaders();
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/lib/components/EditGoal.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<script lang="ts">
// import axios from 'axios';
// import { page } from '$app/stores';
import { onMount, createEventDispatcher } from 'svelte';

// NOTE: goalId will be used to get the goal to edit once this is setup
// const goalId = $page.url.searchParams.get('id');

let daysOfWeek = [
{ name: 'Monday', selected: false },
{ name: 'Tuesday', selected: false },
Expand Down
214 changes: 214 additions & 0 deletions frontend/src/lib/components/GoalCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<script lang="ts">
import type { GoalOutput } from '$lib/generated';
import DaysOfWeekSelector from '$lib/components/DaysOfWeekSelector.svelte';

export let goal: GoalOutput;
</script>

<div
class="collapse md:ml-3 mb-3 bg-primary text-primary-content focus:bg-secondary focus:text-secondary-content"
>
<div class="collapse-title">
<div
class="container shadow-lg rounded-xl mb-4 mx-auto px-4 pt-5 md:max-w-xl lg:max-w-3xl z-10"
>
<div class="mb-5">
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<div class="flex items-center mb-2">
<a href="/edit-goal/{goal.id}" aria-label="edit-goal" title="Edit Goal"> Goal </a>
<div class="dropdown dropdown-right">
<button class="btn btn-circle btn-ghost btn-xs text-info">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="w-4 h-4 stroke-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</button>
<button
class="card compact text-left text-primary dropdown-content z-[1] shadow bg-base-100 rounded-box w-64"
>
<div class="card-body">
<p>Click the Goal Text to the left to edit your SMART goal.</p>
</div>
</button>
</div>
</div>
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
>
{goal.goal}
</div>
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<div class="collapse-content">
<div
class="container shadow-lg rounded-xl mb-4 mx-auto px-4 pt-5 md:max-w-xl lg:max-w-3xl z-10"
>
<!-- Rest of the Content goes here -->
<!-- Specific card -->
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<h2 class="card-title mb-2">Specific</h2>
<div class="flex flex-col md:flex-row w-full">
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline flex-grow mb-2 md:mb-0"
>
{#if goal.specific}
{goal.specific}
{:else}
Not set
{/if}
</div>
</div>
</figcaption>
</figure>
</div>

<!-- Measurable card -->
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<h2 class="card-title mb-2">Measurable</h2>
<div class="flex flex-col md:flex-row w-full">
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline flex-grow mb-2 md:mb-0"
>
{#if goal.measurable}
{goal.measurable}
{:else}
Not set
{/if}
</div>
</div>
</figcaption>
</figure>
</div>

<!-- Attainable card -->
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<h2 class="card-title mb-2">Attainable</h2>
<div class="flex flex-col md:flex-row w-full">
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline flex-grow mb-2 md:mb-0"
>
{#if goal.attainable}
{goal.attainable}
{:else}
Not set
{/if}
</div>
</div>
</figcaption>
</figure>
</div>

<!-- Relevant card -->
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<h2 class="card-title mb-2">Relevant</h2>
<div class="flex flex-col md:flex-row w-full">
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline flex-grow mb-2 md:mb-0"
>
{#if goal.relevant}
{goal.relevant}
{:else}
Not set
{/if}
</div>
</div>
</figcaption>
</figure>
</div>

<!-- Time-Bound card -->
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col">
<h2 class="card-title mb-2">Time-Bound</h2>
<div class="flex flex-col md:flex-row w-full">
<div
class="border rounded-xl w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline flex-grow mb-2 md:mb-0"
>
{#if goal.time_bound}
{goal.time_bound}
{:else}
Not set
{/if}
</div>
</div>
</figcaption>
</figure>
</div>

<div class="mt-3 flex flex-col items-center">
<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-col items-center">
{#if goal.days_of_week}
<div class="flex justify-between items-center w-full">
<h2 class="card-title mb-2">Days</h2>
</div>
<DaysOfWeekSelector daysOfWeek={goal.days_of_week} />
{/if}
</figcaption>
</figure>
</div>
</div>

<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-row items-center">
<h2 class="card-title mb-2">Date</h2>
<div class="flex-grow flex items-center relative">
<input
class="shadow text-secondary appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="goal-date"
type="date"
bind:value={goal.date_for_achievement}
aria-describedby="date-description"
readonly
/>
</div>
</figcaption>
</figure>
</div>

<div class="card w-full">
<figure>
<figcaption class="p-4 card-body flex flex-row items-center">
<h2 class="card-title mb-2">Time</h2>
<div class="flex-grow flex items-center">
<input
class="shadow text-secondary appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="goal-time"
type="time"
bind:value={goal.time_of_day}
aria-describedby="time-description"
readonly
/>
</div>
</figcaption>
</figure>
</div>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions frontend/src/lib/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type { GoalCreate } from './models/GoalCreate';
export type { GoalInfo } from './models/GoalInfo';
export type { GoalInput } from './models/GoalInput';
export type { GoalOutput } from './models/GoalOutput';
export { GoalStatus } from './models/GoalStatus';
export type { GoalSuggestionCreate } from './models/GoalSuggestionCreate';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { PasswordReset } from './models/PasswordReset';
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/generated/models/GoalCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/* eslint-disable */

import type { DaysOfWeekInput } from './DaysOfWeekInput';
import type { GoalStatus } from './GoalStatus';

export type GoalCreate = {
goal?: string | null;
Expand All @@ -16,4 +17,5 @@ export type GoalCreate = {
days_of_week?: DaysOfWeekInput | null;
time_of_day?: string | null;
progress?: number | null;
status?: GoalStatus;
};
2 changes: 2 additions & 0 deletions frontend/src/lib/generated/models/GoalInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/* eslint-disable */

import type { DaysOfWeekInput } from './DaysOfWeekInput';
import type { GoalStatus } from './GoalStatus';

export type GoalInput = {
goal: string;
Expand All @@ -16,5 +17,6 @@ export type GoalInput = {
days_of_week?: DaysOfWeekInput | null;
time_of_day?: string | null;
progress?: number | null;
status?: GoalStatus;
id: string;
};
2 changes: 2 additions & 0 deletions frontend/src/lib/generated/models/GoalOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/* eslint-disable */

import type { DaysOfWeekOutput } from './DaysOfWeekOutput';
import type { GoalStatus } from './GoalStatus';

export type GoalOutput = {
goal: string;
Expand All @@ -16,5 +17,6 @@ export type GoalOutput = {
days_of_week: DaysOfWeekOutput | null;
time_of_day: string | null;
progress: number | null;
status: GoalStatus;
id: string;
};
Loading