// src/types.ts export interface Store { readonly store_id: number; name: string; logo_url?: string | null; readonly created_by?: string | null; readonly created_at: string; readonly updated_at: string; } export type FlyerStatus = 'processed' | 'needs_review' | 'archived'; export interface Flyer { readonly flyer_id: number; file_name: string; image_url: string; icon_url: string; // URL for the 64x64 icon version of the flyer readonly checksum?: string; readonly store_id?: number; valid_from?: string | null; valid_to?: string | null; store_address?: string | null; status: FlyerStatus; item_count: number; readonly uploaded_by?: string | null; // UUID of the user who uploaded it, can be null for anonymous uploads store?: Store; readonly created_at: string; readonly updated_at: string; } /** * Represents the data required to insert a new flyer into the database. * It's a subset of the full Flyer type, excluding generated fields like `flyer_id`. */ export interface FlyerInsert { file_name: string; image_url: string; icon_url: string; checksum: string; store_name: string; valid_from: string | null; valid_to: string | null; store_address: string | null; status: FlyerStatus; item_count: number; uploaded_by?: string | null; } /** * Represents the data required to insert a new flyer into the database, with store_id resolved. * This is an internal type for the database service. */ export type FlyerDbInsert = Omit & { store_id: number }; /** * Represents the data required to insert a new flyer item into the database. * It's a subset of the full FlyerItem type. */ export type FlyerItemInsert = Omit< FlyerItem, 'flyer_item_id' | 'flyer_id' | 'created_at' | 'updated_at' >; export interface UnitPrice { value: number; unit: string; // e.g., 'g', 'kg', 'ml', 'l', 'oz', 'lb', 'each' } export interface FlyerItem { readonly flyer_item_id: number; readonly flyer_id: number; item: string; price_display: string; price_in_cents?: number | null; quantity: string; quantity_num?: number | null; master_item_id?: number; // Can be updated by admin correction master_item_name?: string | null; category_id?: number | null; // Can be updated by admin correction category_name?: string | null; unit_price?: UnitPrice | null; product_id?: number | null; // Can be updated by admin correction readonly view_count: number; readonly click_count: number; readonly created_at: string; readonly updated_at: string; } export interface MasterGroceryItem { readonly master_grocery_item_id: number; name: string; category_id?: number | null; // Can be updated by admin category_name?: string | null; is_allergen?: boolean; allergy_info?: unknown | null; // JSONB readonly created_by?: string | null; readonly created_at: string; readonly updated_at: string; } export interface Category { readonly category_id: number; name: string; readonly created_at: string; readonly updated_at: string; } export interface Brand { readonly brand_id: number; name: string; logo_url?: string | null; readonly store_id?: number | null; store_name?: string | null; readonly created_at: string; readonly updated_at: string; } export interface Product { readonly product_id: number; readonly master_item_id: number; readonly brand_id?: number | null; name: string; description?: string | null; size?: string | null; upc_code?: string | null; readonly created_at: string; readonly updated_at: string; } export interface DealItem { item: string; price_display: string; price_in_cents: number | null; quantity: string; storeName: string; master_item_name?: string | null; unit_price?: UnitPrice | null; } // User-specific types export interface User { readonly user_id: string; // UUID email: string; readonly created_at: string; readonly updated_at: string; } /** * Represents the user data including the password hash, used for authentication checks. * This type is internal to the backend and should not be sent to the client. */ export interface UserWithPasswordHash extends User { password_hash: string | null; readonly failed_login_attempts: number; readonly last_failed_login: string | null; // TIMESTAMPTZ readonly last_login_at?: string | null; // TIMESTAMPTZ readonly last_login_ip?: string | null; } export interface Profile { full_name?: string | null; avatar_url?: string | null; address_id?: number | null; // Can be updated readonly points: number; readonly role: 'admin' | 'user'; preferences?: { darkMode?: boolean; unitSystem?: 'metric' | 'imperial'; } | null; readonly created_by?: string | null; readonly updated_by?: string | null; readonly created_at: string; readonly updated_at: string; } /** * Represents the combined user and profile data object returned by the backend's /users/profile endpoint. * It embeds the User object within the Profile object. * It also includes the full Address object if one is associated with the profile. */ export type UserProfile = Profile & { user: User; address?: Address | null; }; export interface SuggestedCorrection { readonly suggested_correction_id: number; readonly flyer_item_id: number; readonly user_id: string; correction_type: string; suggested_value: string; status: 'pending' | 'approved' | 'rejected'; readonly reviewed_at?: string | null; reviewed_notes?: string | null; readonly created_at: string; readonly updated_at: string; // Joined data user_email?: string; flyer_item_name?: string; flyer_item_price_display?: string; } /** * Represents the complete data package for a user export. */ export interface UserDataExport { profile: Profile; watchedItems: MasterGroceryItem[]; shoppingLists: ShoppingList[]; // Add other user-specific data models here as they are implemented // e.g., pantryItems: PantryItem[]; // e.g., recipes: Recipe[]; } export interface UserAlert { readonly user_alert_id: number; readonly user_watched_item_id: number; alert_type: 'PRICE_BELOW' | 'PERCENT_OFF_AVERAGE'; threshold_value: number; is_active: boolean; readonly created_at: string; readonly updated_at: string; } export interface Notification { readonly notification_id: number; readonly user_id: string; // UUID content: string; link_url?: string | null; is_read: boolean; readonly created_at: string; readonly updated_at: string; } export interface ShoppingList { readonly shopping_list_id: number; readonly user_id: string; // UUID name: string; items: ShoppingListItem[]; // Nested items readonly created_at: string; readonly updated_at: string; } export interface ShoppingListItem { readonly shopping_list_item_id: number; readonly shopping_list_id: number; readonly master_item_id?: number | null; custom_item_name?: string | null; quantity: number; is_purchased: boolean; notes?: string | null; readonly added_at: string; readonly updated_at: string; // Joined data for display master_item?: { name: string; } | null; } export interface UserSubmittedPrice { readonly user_submitted_price_id: number; readonly user_id: string; // UUID readonly master_item_id: number; readonly store_id: number; price_in_cents: number; photo_url?: string | null; readonly upvotes: number; readonly downvotes: number; readonly created_at: string; readonly updated_at: string; } export interface ItemPriceHistory { readonly item_price_history_id: number; readonly master_item_id: number; summary_date: string; // DATE readonly store_location_id?: number | null; min_price_in_cents?: number | null; max_price_in_cents?: number | null; avg_price_in_cents?: number | null; data_points_count: number; readonly created_at: string; readonly updated_at: string; } /** * Represents a single data point for an item's price on a specific day. * This is the raw structure returned by the price history API endpoint. */ export interface HistoricalPriceDataPoint { master_item_id: number; avg_price_in_cents: number | null; summary_date: string; // DATE } export interface MasterItemAlias { readonly master_item_alias_id: number; readonly master_item_id: number; alias: string; readonly created_at: string; readonly updated_at: string; } export interface Recipe { readonly recipe_id: number; readonly user_id?: string | null; // UUID readonly original_recipe_id?: number | null; name: string; description?: string | null; instructions?: string | null; prep_time_minutes?: number | null; cook_time_minutes?: number | null; servings?: number | null; photo_url?: string | null; calories_per_serving?: number | null; protein_grams?: number | null; fat_grams?: number | null; carb_grams?: number | null; readonly avg_rating: number; status: 'private' | 'pending_review' | 'public' | 'rejected'; readonly rating_count: number; readonly fork_count: number; comments?: RecipeComment[]; ingredients?: RecipeIngredient[]; readonly created_at: string; readonly updated_at: string; } export interface RecipeIngredient { readonly recipe_ingredient_id: number; readonly recipe_id: number; readonly master_item_id: number; quantity: number; unit: string; readonly created_at: string; readonly updated_at: string; } export interface RecipeIngredientSubstitution { readonly recipe_ingredient_substitution_id: number; readonly recipe_ingredient_id: number; readonly substitute_master_item_id: number; notes?: string | null; readonly created_at: string; readonly updated_at: string; } export interface Tag { readonly tag_id: number; name: string; readonly created_at: string; readonly updated_at: string; } export interface RecipeTag { recipe_id: number; tag_id: number; } export interface RecipeRating { readonly recipe_rating_id: number; readonly recipe_id: number; readonly user_id: string; // UUID rating: number; comment?: string | null; readonly created_at: string; readonly updated_at: string; } export interface RecipeComment { readonly recipe_comment_id: number; readonly recipe_id: number; readonly user_id: string; // UUID readonly parent_comment_id?: number | null; content: string; status: 'visible' | 'hidden' | 'reported'; readonly created_at: string; readonly updated_at: string; user_full_name?: string; // Joined data user_avatar_url?: string; // Joined data } export interface MenuPlan { readonly menu_plan_id: number; readonly user_id: string; // UUID name: string; start_date: string; // DATE end_date: string; // DATE planned_meals?: PlannedMeal[]; readonly created_at: string; readonly updated_at: string; } export interface SharedMenuPlan { readonly shared_menu_plan_id: number; readonly menu_plan_id: number; readonly shared_by_user_id: string; // UUID readonly shared_with_user_id: string; // UUID permission_level: 'view' | 'edit'; readonly created_at: string; readonly updated_at: string; } export interface PlannedMeal { readonly planned_meal_id: number; readonly menu_plan_id: number; readonly recipe_id: number; plan_date: string; // DATE meal_type: string; servings_to_cook?: number | null; readonly created_at: string; readonly updated_at: string; } export interface PantryItem { readonly pantry_item_id: number; readonly user_id: string; // UUID readonly master_item_id: number; quantity: number; unit?: string | null; best_before_date?: string | null; // DATE pantry_location_id?: number | null; readonly notification_sent_at?: string | null; // TIMESTAMPTZ readonly updated_at: string; } export interface UserItemAlias { readonly user_item_alias_id: number; readonly user_id: string; // UUID readonly master_item_id: number; alias: string; readonly created_at: string; readonly updated_at: string; } export interface FavoriteRecipe { readonly user_id: string; // UUID readonly recipe_id: number; readonly created_at: string; } export interface FavoriteStore { readonly user_id: string; // UUID readonly store_id: number; readonly created_at: string; } export interface RecipeCollection { readonly recipe_collection_id: number; readonly user_id: string; // UUID name: string; description?: string | null; readonly created_at: string; readonly updated_at: string; } export interface RecipeCollectionItem { readonly collection_id: number; readonly recipe_id: number; readonly added_at: string; } export interface SharedShoppingList { readonly shared_shopping_list_id: number; readonly shopping_list_id: number; readonly shared_by_user_id: string; // UUID readonly shared_with_user_id: string; // UUID permission_level: 'view' | 'edit'; readonly created_at: string; readonly updated_at: string; } export interface SharedRecipeCollection { readonly shared_collection_id: number; readonly recipe_collection_id: number; readonly shared_by_user_id: string; // UUID readonly shared_with_user_id: string; // UUID permission_level: 'view' | 'edit'; readonly created_at: string; readonly updated_at: string; } export interface DietaryRestriction { readonly dietary_restriction_id: number; name: string; type: 'diet' | 'allergy'; readonly created_at: string; readonly updated_at: string; } export interface UserDietaryRestriction { readonly user_id: string; // UUID readonly restriction_id: number; readonly created_at: string; } export interface Appliance { readonly appliance_id: number; name: string; readonly created_at: string; readonly updated_at: string; } export interface UserAppliance { readonly user_id: string; // UUID readonly appliance_id: number; readonly created_at: string; } export interface RecipeAppliance { readonly recipe_id: number; readonly appliance_id: number; readonly created_at: string; } export interface UserFollow { readonly follower_id: string; // UUID readonly following_id: string; // UUID readonly created_at: string; } /** * The list of possible actions for an activity log. * Using a specific type union instead of a generic 'string' allows for better type checking. */ export type ActivityLogAction = | 'flyer_processed' | 'recipe_created' | 'user_registered' | 'recipe_favorited' | 'list_shared' | 'login_failed_password' | 'password_reset'; /** * Base interface for all log items, containing common properties. */ interface ActivityLogItemBase { readonly activity_log_id: number; readonly user_id: string | null; action: ActivityLogAction; display_text: string; icon?: string | null; readonly created_at: string; readonly updated_at: string; // Joined data for display in feeds user_full_name?: string; user_avatar_url?: string; } // --- Discriminated Union for Activity Log Details --- interface FlyerProcessedLog extends ActivityLogItemBase { action: 'flyer_processed'; details: { flyer_id: number; store_name: string; }; } interface RecipeCreatedLog extends ActivityLogItemBase { action: 'recipe_created'; details: { recipe_id: number; recipe_name: string; }; } interface UserRegisteredLog extends ActivityLogItemBase { action: 'user_registered'; details: { full_name: string; }; } interface RecipeFavoritedLog extends ActivityLogItemBase { action: 'recipe_favorited'; details: { recipe_name: string; }; } interface ListSharedLog extends ActivityLogItemBase { action: 'list_shared'; details: { list_name: string; shopping_list_id: number; shared_with_name: string; }; } /** * The final ActivityLogItem type is a union of all specific log types. * TypeScript will now correctly infer the shape of 'details' based on the 'action' property. */ export type ActivityLogItem = | FlyerProcessedLog | RecipeCreatedLog | UserRegisteredLog | RecipeFavoritedLog | ListSharedLog; export interface PantryLocation { readonly pantry_location_id: number; readonly user_id: string; // UUID name: string; readonly created_at: string; readonly updated_at: string; } export interface SearchQuery { readonly search_query_id: number; readonly user_id?: string | null; // UUID query_text: string; result_count?: number | null; was_successful?: boolean | null; readonly created_at: string; readonly updated_at: string; } export interface ShoppingTripItem { readonly shopping_trip_item_id: number; readonly shopping_trip_id: number; readonly master_item_id?: number | null; custom_item_name?: string | null; quantity: number; price_paid_cents?: number | null; readonly created_at: string; readonly updated_at: string; // Joined data for display master_item_name?: string | null; } export interface ShoppingTrip { readonly shopping_trip_id: number; readonly user_id: string; // UUID readonly shopping_list_id?: number | null; readonly completed_at: string; total_spent_cents?: number | null; items: ShoppingTripItem[]; // Nested items readonly updated_at: string; } export interface Receipt { readonly receipt_id: number; readonly user_id: string; // UUID store_id?: number | null; receipt_image_url: string; transaction_date?: string | null; total_amount_cents?: number | null; status: 'pending' | 'processing' | 'completed' | 'failed'; raw_text?: string | null; readonly processed_at?: string | null; items?: ReceiptItem[]; readonly created_at: string; readonly updated_at: string; } export interface ReceiptItem { readonly receipt_item_id: number; readonly receipt_id: number; raw_item_description: string; quantity: number; price_paid_cents: number; master_item_id?: number | null; // Can be updated by admin correction product_id?: number | null; // Can be updated by admin correction status: 'unmatched' | 'matched' | 'needs_review' | 'ignored'; readonly created_at: string; readonly updated_at: string; } export interface ReceiptDeal { readonly receipt_item_id: number; readonly master_item_id: number; item_name: string; price_paid_cents: number; current_best_price_in_cents: number; potential_savings_cents: number; deal_store_name: string; flyer_id: number; } /** * Represents a geographic point in GeoJSON format. * This is a standard way to represent point data from PostGIS. */ export interface GeoJSONPoint { type: 'Point'; coordinates: [number, number]; // [longitude, latitude] } export interface StoreLocation { readonly store_location_id: number; readonly store_id?: number | null; readonly address_id: number; readonly created_at: string; readonly updated_at: string; } export interface Address { readonly address_id: number; address_line_1: string; address_line_2?: string | null; city: string; province_state: string; postal_code: string; country: string; latitude?: number | null; longitude?: number | null; readonly location?: GeoJSONPoint | null; readonly created_at: string; readonly updated_at: string; } export interface FlyerLocation { readonly flyer_id: number; readonly store_location_id: number; readonly created_at: string; readonly updated_at: string; } export enum AnalysisType { QUICK_INSIGHTS = 'QUICK_INSIGHTS', DEEP_DIVE = 'DEEP_DIVE', WEB_SEARCH = 'WEB_SEARCH', PLAN_TRIP = 'PLAN_TRIP', GENERATE_IMAGE = 'GENERATE_IMAGE', COMPARE_PRICES = 'COMPARE_PRICES', } /** * Represents a source for a grounded response, normalized for consistent use in the UI. */ export interface Source { uri: string; title: string; } /** * Represents a response that may include sources, such as from a web search or map plan. */ export interface GroundedResponse { text: string; sources: Source[]; } /** * Defines the shape of the state managed by the useAiAnalysis hook's reducer. * This centralizes all state related to AI analysis into a single, predictable object. */ export interface AiAnalysisState { // The type of analysis currently being performed, if any. loadingAnalysis: AnalysisType | null; // A general error message for any failed analysis. error: string | null; // Stores the text result for each analysis type. results: { [key in AnalysisType]?: string }; // Stores the sources for analyses that provide them. sources: { [key in AnalysisType]?: Source[] }; // Stores the URL of the last generated image. generatedImageUrl: string | null; } /** * Defines the actions that can be dispatched to the AiAnalysisReducer. * This uses a discriminated union for strict type checking. */ export type AiAnalysisAction = // Dispatched when any analysis starts. | { type: 'FETCH_START'; payload: { analysisType: AnalysisType } } // Dispatched when an analysis that returns a simple string succeeds. | { type: 'FETCH_SUCCESS_TEXT'; payload: { analysisType: AnalysisType; data: string } } // Dispatched when an analysis that returns text and sources succeeds. | { type: 'FETCH_SUCCESS_GROUNDED'; payload: { analysisType: AnalysisType; data: GroundedResponse }; } // Dispatched when the image generation succeeds. | { type: 'FETCH_SUCCESS_IMAGE'; payload: { data: string } } // Dispatched when any analysis fails. | { type: 'FETCH_ERROR'; payload: { error: string } } // Dispatched to clear errors or reset state if needed. | { type: 'CLEAR_ERROR' } // Dispatched to reset the state to its initial values. | { type: 'RESET_STATE' }; export type StageStatus = 'pending' | 'in-progress' | 'completed' | 'error'; export interface ProcessingStage { name: string; status: StageStatus; detail?: string; critical?: boolean; progress?: { current: number; total: number } | null; } export const CATEGORIES = [ 'Fruits & Vegetables', 'Meat & Seafood', 'Dairy & Eggs', 'Bakery & Bread', 'Pantry & Dry Goods', 'Beverages', 'Frozen Foods', 'Snacks', 'Household & Cleaning', 'Personal Care & Health', 'Baby & Child', 'Pet Supplies', 'Deli & Prepared Foods', 'Canned Goods', 'Condiments & Spices', 'Breakfast & Cereal', 'Organic', 'International Foods', 'Other/Miscellaneous', ]; /** * Represents the core data extracted from a flyer by the AI service. * This is the structure returned from the backend to the frontend. */ export interface ExtractedCoreData { store_name: string; valid_from: string | null; valid_to: string | null; store_address: string | null; items: ExtractedFlyerItem[]; } /** * Represents the shape of a single flyer item as returned by the AI service, * before it is saved to the database. This is a Data Transfer Object (DTO). * It intentionally omits database-generated fields like `flyer_item_id`, `created_at`, etc. */ export interface ExtractedFlyerItem { item: string; price_display: string; price_in_cents: number | null; quantity: string; category_name: string; master_item_id?: number; // Kept optional as AI might not find a match unit_price?: UnitPrice | null; } /** * Represents the logo data extracted from a flyer by the AI service. */ export interface ExtractedLogoData { store_logo_base_64: string | null; } /** * Represents the data extracted from a receipt image by the AI service. */ export interface ExtractedReceiptData { raw_text: string; items: { raw_item_description: string; quantity: number; price_paid_cents: number; }[]; } /** * Represents an item that frequently appears on sale. * Returned by the `get_most_frequent_sale_items` database function. */ export interface MostFrequentSaleItem { master_item_id: number; item_name: string; sale_count: number; } /** * Represents a recipe that can be made from items in a user's pantry. * Returned by the `find_recipes_from_pantry` database function. */ export interface PantryRecipe extends Recipe { missing_ingredients_count: number; pantry_ingredients_count: number; } /** * Represents a recommended recipe for a user. * Returned by the `recommend_recipes_for_user` database function. */ export interface RecommendedRecipe extends Recipe { recommendation_score: number; reason: string; } /** * Represents the best current sale price for a user's watched item. * Returned by the `get_best_sale_prices_for_user` database function. */ export interface WatchedItemDeal { master_item_id: number; item_name: string; best_price_in_cents: number; store_name: string; flyer_id: number; valid_to: string; // Date string } /** * Represents a suggested unit conversion for a pantry item. * Returned by the `suggest_pantry_item_conversions` database function. */ export interface PantryItemConversion { to_unit: string; converted_quantity: number; } /** * Represents an item needed for a menu plan, considering pantry stock. * Returned by `generate_shopping_list_for_menu_plan` and `add_menu_plan_to_shopping_list`. */ export interface MenuPlanShoppingListItem { master_item_id: number; item_name: string; quantity_needed: number; } /** * Represents an unmatched flyer item pending manual review. * Returned by `getUnmatchedFlyerItems`. */ export interface UnmatchedFlyerItem { readonly unmatched_flyer_item_id: number; status: 'pending' | 'resolved' | 'ignored'; // 'resolved' is used instead of 'reviewed' from the DB for clarity readonly reviewed_at?: string | null; readonly flyer_item_id: number; flyer_item_name: string; price_display: string; flyer_id: number; store_name: string; readonly created_at: string; readonly updated_at: string; } /** * Represents a user-defined budget for tracking grocery spending. */ export interface Budget { readonly budget_id: number; readonly user_id: string; // UUID name: string; amount_cents: number; period: 'weekly' | 'monthly'; start_date: string; // DATE readonly created_at: string; readonly updated_at: string; } /** * Represents the aggregated spending for a single category. * Returned by the `get_spending_by_category` database function. */ export interface SpendingByCategory { category_id: number; category_name: string; total_spent_cents: number; } /** * Represents a single defined achievement in the system. */ export interface Achievement { readonly achievement_id: number; name: string; description: string; icon?: string | null; points_value: number; readonly created_at: string; } /** * Represents an achievement that has been awarded to a user. */ export interface UserAchievement { readonly user_id: string; // UUID readonly achievement_id: number; readonly achieved_at: string; // TIMESTAMPTZ } /** * Represents a user's entry on the leaderboard. * Returned by the `getLeaderboard` database function. */ export interface LeaderboardUser { readonly user_id: string; full_name: string | null; avatar_url: string | null; points: number; readonly rank: string; // RANK() returns a bigint, which the pg driver returns as a string. } /** * Defines the shape of the user data returned for the admin user list. * This is a public-facing type and does not include sensitive fields. */ export interface AdminUserView { readonly user_id: string; email: string; role: 'admin' | 'user'; full_name: string | null; avatar_url: string | null; readonly created_at: string; } export interface PriceHistoryData { master_item_id: number; price_in_cents: number; date: string; // ISO date string } export interface UserReaction { readonly reaction_id: number; readonly user_id: string; // UUID readonly entity_type: string; readonly entity_id: string; reaction_type: string; readonly created_at: string; readonly updated_at: string; } export interface UnitConversion { readonly unit_conversion_id: number; readonly master_item_id: number; from_unit: string; to_unit: string; factor: number; readonly created_at: string; readonly updated_at: string; }