Skip to content
Open
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
7 changes: 5 additions & 2 deletions SparkyFitnessFrontend/src/pages/Diary/Diary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { Card, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
Expand Down Expand Up @@ -63,8 +63,11 @@ const Diary = () => {
const [editingEntry, setEditingEntry] = useState<FoodEntry | null>(null);
const [editingFoodEntryMeal, setEditingFoodEntryMeal] =
useState<FoodEntryMeal | null>(null); // State for editing logged meal entry
const [searchParams] = useSearchParams();

const [selectedDate, setSelectedDate] = useState(
formatDateInUserTimezone(new Date(), 'yyyy-MM-dd')
searchParams.get('date') ??
formatDateInUserTimezone(new Date(), 'yyyy-MM-dd')
);
const [date, setDate] = useState(parseDateInUserTimezone(selectedDate));
debug(loggingLevel, 'FoodDiary component rendered for date:', selectedDate);
Expand Down
12 changes: 10 additions & 2 deletions SparkyFitnessFrontend/src/pages/Diary/MealCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ interface MealTotals {

import type { UserCustomNutrient } from '@/types/customNutrient';
import { DEFAULT_NUTRIENTS } from '@/constants/nutrients';
import { useSearchParams } from 'react-router-dom';
import { cn } from '@/lib/utils';

interface MealCardProps {
meal: {
Expand Down Expand Up @@ -119,7 +121,8 @@ const MealCard = ({
onFoodSearchClose();
}
};

const [searchParams] = useSearchParams();
const highlightFoodId = searchParams.get('highlight') ?? null;
const { mutate: copyFoodEntriesFromYesterday } =
useCopyFoodEntriesFromYesterdayMutation();
const getEnergyUnitString = (unit: 'kcal' | 'kJ'): string => {
Expand Down Expand Up @@ -350,7 +353,12 @@ const MealCard = ({
return (
<div
key={item.id} // Use item.id directly
className="flex flex-col sm:flex-row sm:items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg gap-4"
className={cn(
'flex flex-col sm:flex-row sm:items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg gap-4 transition-colors duration-300',
'food_id' in item &&
(item as FoodEntry).food_id === highlightFoodId &&
'border-2 border-blue-500'
)}
>
<div className="flex-1">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 mb-1">
Expand Down
147 changes: 117 additions & 30 deletions SparkyFitnessFrontend/src/pages/Foods/Foods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ import {
import CustomFoodForm from '@/components/FoodSearch/CustomFoodForm';
import { MealFilter } from '@/types/meal';
import type { Meal } from '@/types/meal';
import { useMealTypes } from '@/hooks/Diary/useMealTypes';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { Link } from 'react-router-dom';

const FoodDatabaseManager: React.FC = () => {
const { t } = useTranslation();
Expand All @@ -70,6 +77,15 @@ const FoodDatabaseManager: React.FC = () => {
const isMobile = useIsMobile();
const platform = isMobile ? 'mobile' : 'desktop';

const formatEntryDate = (date: Date | string) =>
new Date(date).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});

const toISODate = (date: Date | string) =>
new Date(date).toISOString().split('T')[0];
const quickInfoPreferences =
nutrientDisplayPreferences.find(
(p) => p.view_group === 'quick_info' && p.platform === platform
Expand Down Expand Up @@ -109,6 +125,8 @@ const FoodDatabaseManager: React.FC = () => {
const { mutateAsync: deleteFood } = useDeleteFoodMutation();
const { mutateAsync: createFoodEntry } = useCreateFoodMutation();

const { data: mealTypes } = useMealTypes();

const handleDeleteRequest = async (food: Food) => {
if (!user || !activeUserId) return;
const impact = await queryClient.fetchQuery(
Expand Down Expand Up @@ -702,7 +720,7 @@ const FoodDatabaseManager: React.FC = () => {
open={showDeleteConfirmation}
onOpenChange={setShowDeleteConfirmation}
>
<DialogContent>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>
{t('foodDatabaseManager.deleteFoodConfirmTitle', {
Expand All @@ -711,42 +729,110 @@ const FoodDatabaseManager: React.FC = () => {
})}
</DialogTitle>
</DialogHeader>
<div>
<p>

<div className="space-y-3">
<p className="text-sm text-muted-foreground">
{t('foodDatabaseManager.foodUsedIn', 'This food is used in:')}
</p>
<ul className="list-disc pl-5 mt-2">
<li>
{t('foodDatabaseManager.diaryEntries', {
count: deletionImpact.foodEntriesCount,
defaultValue: `${deletionImpact.foodEntriesCount} diary entries`,
})}
</li>
<li>
{t('foodDatabaseManager.mealComponents', {

<div className="space-y-1.5">
{/* Diary entries — collapsible */}
{deletionImpact.foodEntries.length > 0 ? (
<Collapsible
defaultOpen
className="rounded-lg border border-border overflow-hidden"
>
<CollapsibleTrigger className="w-full flex items-center justify-between px-3 py-2.5 text-sm font-medium bg-muted/40 hover:bg-muted/70 transition-colors text-left">
<span>
{t('foodDatabaseManager.diaryEntries', {
count: deletionImpact.foodEntriesCount,
defaultValue: `${deletionImpact.foodEntriesCount} diary entries`,
})}
</span>
<span className="text-muted-foreground text-xs">▼</span>
</CollapsibleTrigger>

<CollapsibleContent>
<div className="max-h-56 overflow-y-auto divide-y divide-border">
{deletionImpact.foodEntries.map((entry) => (
<div
key={entry.id}
className="flex items-center gap-4 px-3 py-2 text-sm hover:bg-muted/30 transition-colors"
>
<span className="w-28 shrink-0 font-medium tabular-nums">
{formatEntryDate(entry.entry_date)}
</span>
<span className="flex-1 text-muted-foreground capitalize">
{mealTypes?.find(
(mt) => mt.id === entry.meal_type_id
)?.name ?? '—'}
</span>
{entry.isCurrentUser ? (
<Link
to={
'/?date=' +
toISODate(entry.entry_date) +
'&highlight=' +
foodToDelete.id
}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-primary underline underline-offset-2 shrink-0 hover:text-primary/70 transition-colors cursor-pointer"
>
View
</Link>
) : (
<span className="text-[10px] text-muted-foreground shrink-0 px-1.5 py-0.5 rounded-full bg-muted">
other user
</span>
)}
</div>
))}
</div>
</CollapsibleContent>
</Collapsible>
) : (
<div className="rounded-lg border border-border px-3 py-2.5 text-sm text-muted-foreground bg-muted/20">
{t('foodDatabaseManager.diaryEntries', {
count: 0,
defaultValue: '0 diary entries',
})}
</div>
)}
{[
{
key: 'mealComponents',
count: deletionImpact.mealFoodsCount,
defaultValue: `${deletionImpact.mealFoodsCount} meal components`,
})}
</li>
<li>
{t('foodDatabaseManager.mealPlanEntries', {
label: 'meal components',
},
{
key: 'mealPlanEntries',
count: deletionImpact.mealPlansCount,
defaultValue: `${deletionImpact.mealPlansCount} meal plan entries`,
})}
</li>
<li>
{t('foodDatabaseManager.mealPlanTemplateEntries', {
label: 'meal plan entries',
},
{
key: 'mealPlanTemplateEntries',
count: deletionImpact.mealPlanTemplateAssignmentsCount,
defaultValue: `${deletionImpact.mealPlanTemplateAssignmentsCount} meal plan template entries`,
})}
</li>
</ul>
label: 'meal plan template entries',
},
]
.filter(({ count }) => count > 0)
.map(({ key, count }) => (
<div
key={key}
className="rounded-lg border border-border px-3 py-2.5 text-sm bg-muted/20"
>
<span>{t('foodDatabaseManager.' + key, { count })}</span>
</div>
))}
</div>
Comment thread
Sim-sat marked this conversation as resolved.

{deletionImpact.otherUserReferences > 0 && (
<div className="mt-4 p-4 bg-yellow-100 text-yellow-800 rounded-md">
<p className="font-bold">
<div className="p-3.5 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-300 rounded-lg text-sm space-y-1">
<p className="font-semibold">
{t('foodDatabaseManager.warning', 'Warning!')}
</p>
<p>
<p className="text-yellow-700 dark:text-yellow-400">
{t(
'foodDatabaseManager.foodUsedByOtherUsersWarning',
'This food is used by other users. You can only hide it. Hiding will prevent other users from adding this food in the future, but it will not affect their existing history, meals, or meal plans.'
Expand All @@ -755,7 +841,8 @@ const FoodDatabaseManager: React.FC = () => {
</div>
)}
</div>
<div className="flex justify-end space-x-2 mt-4">

<div className="flex justify-end space-x-2 mt-2">
<Button
variant="outline"
onClick={() => setShowDeleteConfirmation(false)}
Expand Down
6 changes: 6 additions & 0 deletions SparkyFitnessFrontend/src/types/food.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface Food {
}

export interface FoodDeletionImpact {
foodEntries: FoodEntryDeletionImpact[];
foodEntriesCount: number;
mealFoodsCount: number;
mealPlansCount: number;
Expand All @@ -70,6 +71,11 @@ export interface FoodDeletionImpact {
familySharedUsers: string[];
}

export type FoodEntryDeletionImpact = Pick<
FoodEntry,
'id' | 'entry_date' | 'meal_type_id'
> & { isCurrentUser: boolean };

export interface FoodEntry {
id: string;
food_id?: string; // Make optional as it might be a meal_id
Expand Down
Loading
Loading