| 1 | == ID: 1 |
| 2 | 1. Корисникот пишува во полето за пребарување состојки, што активира AJAX повик до /Recipes/getSuggestions акцијата по 300ms задоцнување за да ги добие соодветните состојки од базата на податоци. |
| 3 | 2. searchIngredients() прави fetch(/Recipes/getSuggestions?query=${encodeURIComponent(query)}) со GET метод. Одговорот се парсира со response.json() и се прикажува преку displaySuggestions(). |
| 4 | 3. displaySuggestions() користи document.createElement("div") за секој резултат, сетира innerHTML со regex name.replace(regex, "<strong>$1</strong>") за highlighting, и поставува addEventListener("click") за селекција. |
| 5 | 4. Избраните состојки се чуваат во selectedIngredients[] низа со структура {Id, Name, Quantity, Unit}. addIngredient() прави selectedIngredients.push(newIngredient) и повикува updateIngredientsDisplay() и updateIngredientsInput() |
| 6 | 5. updateIngredientsInput() користи JSON.stringify(selectedIngredients) за да ја конвертира низата во string и ги сетира во hiddenIngredientsInput.value за испраќање преку формата |
| 7 | 6. initializeFileUpload() поставува addEventListener("dragover"), addEventListener("drop"), и FileReader() API за preview на сликата со reader.readAsDataURL(file) |
| 8 | 7. showAddIngredientModal() го прикажува модалниот прозорец за додавање на нова состојка што ја нема во базата. handleAddNewIngredient() прави POST до /Recipes/AddIngredient каде состојката се додава во базата но со "Pending" статус |
| 9 | 8. При испраќање, контролерот: |
| 10 | - Ја зачувува прикачената слика во /wwwroot/images/ со уникатно име |
| 11 | - Креира запис за рецептот со ID на корисникот и основни информации |
| 12 | - Ја изминува низата од состојките за да креира записи во RecipeIngredient |
| 13 | - Пресметува вкупни нутритривни вредности користејќи ConvertType() метод |
| 14 | - Генерира тагови за типот на рецептот користејќи GenerateRecipeTags() |
| 15 | |
| 16 | == ID: 2 |
| 17 | 1. Корисникот го клика рецептот што сака да го избрише од "My Recipes" секцијата |
| 18 | 2. Корисникот клика на копчето за бришење со што се прави повик до /Recipes/Delete акцијата која го враќа "_RecipeDeletePartial" |
| 19 | 3. Корисникот го потврдува бришењето на рецептот со што се прави повик до /Recipes/DeleteConfirmed акцијата која го брише записот за избраниот рецепт |
| 20 | |
| 21 | == ID: 3 |
| 22 | 1. /Recipes/Index акцијата користи .Where(r => r.RecipeStatus == "Accepted") за да ги филтрира одобрените рецепти, потоа .Skip((page - 1) * pageSize).Take(pageSize) за пагинација. |
| 23 | 2. initializeInfiniteScroll() поставува event listener кој проверува scrollTop + windowHeight >= documentHeight - 600 за да открие кога корисникот е близу крајот. loadMoreRecipes() испраќа GET барање до /Recipes/Index акцијата. |
| 24 | 3. createRecipeCard() динамички креира div елементи со data-calories, data-protein, data-carbs, data-fat атрибути кои се користат за филтрирање. |
| 25 | 4. updateSlider() чита parseInt(minSlider.value) и parseInt(maxSlider.value), ги споредува вредностите со if (minVal > maxVal), и сетира CSS left и width на .range-fill елементот за визуелниот приказ на самите слајдери. |
| 26 | 5. filterRecipes() користи document.querySelectorAll(".recipe-card") за да ги добие сите картички, потоа за секоја прави: |
| 27 | - parseInt(card.dataset.calories) за филтрирање според макро вредности |
| 28 | - .textContent.toLowerCase() за текстуално пребарување |
| 29 | - .getAttribute("data-favorited") === "true" за приказ на омилени |
| 30 | |
| 31 | == ID: 4 |
| 32 | 1. currentFilters објектот ги чува вредностите од слајдерите, додека initializeFilters() поставува event listeners на сите 8 slider inputs со addEventListener("input"). |
| 33 | 2. updateSlider() синхронизира min/max вредности со if (minVal > maxVal) validation, ажурира CSS, и повикува applyFilters() за да ги зачува вредностите во currentFilters објектот. |
| 34 | 3. openMenu(restaurantId) се активира на click, конструира query parameters објект од current filter state, и прави new URLSearchParams(f).toString() за URL encoding на филтрите. |
| 35 | 4. fetch(/Restaurants/GetRestaurantMeals/${restaurantId}?${query}) испраќа GET барање со филтер параметри како query string до контролерот. |
| 36 | 5. Контролерот прима nullable integers int? minCalories, int? maxCalories... што ASP.NET ги bind-ува од query string автоматски. |
| 37 | 6. .Where() применува range filtering со логика (minCalories == null || r.Calories >= minCalories) за секој макронутриент. |
| 38 | 7. Контролерот враќа PartialView("_RestaurantMealsPartial", filteredMeals), кој fetch го зема како .text() response. |
| 39 | 8. JavaScript го вметнува HTML од филтрираните оброци во модалот преку menuContainer.innerHTML = html, прикажувајќи само оброци што ги исполнуваат критериумите поставени на клиентската страна. |
| 40 | |
| 41 | == ID: 5 |
| 42 | 1. [BindProperty] InputModel Input автоматски ги bind-ува HTML form полињата со C# својствата. Data annotations [Required], [EmailAddress], [StringLength(100, MinimumLength = 6)] обезбедуваат client и server-side validation. |
| 43 | 2. OnPostAsync() прво проверува ModelState.IsValid за server-side validation, потоа повикува CreateUser() за да креира нова User instance користејќи Activator.CreateInstance<User>() reflection. |
| 44 | 3. _userStore.SetUserNameAsync() и _emailStore.SetEmailAsync() ги сетираат username-от и еmail-от, додека _userManager.CreateAsync(user, Input.Password) го креира записот во базата со хаширан пасворд преку Identity framework-от. |
| 45 | 4. _signInManager.SignInAsync(user, isPersistent: false) автоматски го логира и го пренасочува кон returnUrl. |
| 46 | |
| 47 | == ID: 6 |
| 48 | 1. [BindProperty] InputModel Input автоматски ги bind-ува email и password полињата преку ASP.NET model binding. Data annotations [Required], [EmailAddress], [DataType(DataType.Password)] обезбедуваат валидација. |
| 49 | 2. OnPostAsync() прави ModelState.IsValid проверка, потоа повикува _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false) за автентикација според базата. |
| 50 | 3. result.Succeeded е проверка за успешна најава и прави LocalRedirect(returnUrl). |
| 51 | 4. Успешната најава креира authentication cookie преку Identity framework што се користи при наредни requests. |
| 52 | |
| 53 | == ID: 7 |
| 54 | 1. showRecipeDetails(recipeId) се активира при клик, додава loading класа на картичката, и конструира query parameters со new URLSearchParams() за да прати isOwner и recipeDetailsDisplayContorol параметри. |
| 55 | 2. fetch(/Recipes/Details/${recipeId}?${params}) прави GET request и го зема response како .text() бидејќи се враќа HTML partial view. |
| 56 | 3. GetRatingDataAsync() во контролерот ги пресметува averageRating, totalRatings, userRating, и hasUserRated од database, додека _context.FavoriteRecipes.AnyAsync() проверува favorite статус. |
| 57 | 4. Контролерот ги поставува ViewBag вредности (IsOwner, AverageRating, IsFavorited) што се користат во partial view за условен приказ на елементите. |
| 58 | 5. JavaScript го вметнува partial view-то во modalContainer.innerHTML = html, потоа динамички извршува <script> тагови со document.createElement("script") и document.body.appendChild(). |
| 59 | |
| 60 | == ID: 8 |
| 61 | 1. rateRecipe(rating, recipeId) се активира со click на ѕвездичка, поставува isRatingInProgress = true за да спречи multiple concurrent requests, и веднаш го ажурира UI со updateUserRatingDisplay(rating). |
| 62 | 2. fetch('/Recipes/Rate') испраќа POST request со JSON body {recipeId, rating}. |
| 63 | 3. /Recipes/Rate акцијата користи [FromBody] JsonElement body за JSON deserialization, валидира rating >= 1 && rating <= 5, и проверува authentication со User.FindFirstValue(ClaimTypes.NameIdentifier). |
| 64 | 4. _context.RecipeRatings.FirstOrDefaultAsync() проверува за постоечки rating. Ако постои, прави existingRating.Rating = rating update, инаку креира new RecipeRating entity и го додава со _context.Add(). |
| 65 | 5. По SaveChangesAsync(), контролерот прави _context.RecipeRatings.Where().Select(r => r.Rating).ToListAsync() и пресметува ratings.Average() и ratings.Count за ажурирани податоци. |
| 66 | 6. На клиентска страна .then(response => response.json()) го парсира JSON response, повикува updateAverageRatingDisplay(data.averageRating, data.totalRatings) за да ги ажурира ѕвездичките, и прикажува showRemoveRatingButton(). |
| 67 | 7. removeRating() прави POST до /Recipes/RemoveRating, контролерот го бара постоечкиот запис со FirstOrDefaultAsync() и го брише со _context.Remove(), потоа рекалкулира податоци. |
| 68 | 8. clearUserRatingDisplay() ги ресетира ѕвездичките, hideRemoveRatingButton() го сокрива remove копчето, и document.querySelector('.rate-label').textContent се менува од "Your rating:" во "Rate this recipe:". |
| 69 | |
| 70 | == ID: 9 и ID: 11 |
| 71 | 1. тoggleFavorite(recipeId) поставува isFavoriteInProgress = true, зачувува wasFavorited состојба, и веднаш ја ажурира UI со favoriteBtn.classList.toggle('favorited') и favoriteBtn.title пред да испрати request. |
| 72 | 2. fetch('/Recipes/ToggleFavorite') испраќа POST request со JSON body {recipeId}. |
| 73 | 3. /Recipes/ToggleFavorite акцијата користи [ValidateAntiForgeryToken] attribute, проверува authentication со User.FindFirstValue(ClaimTypes.NameIdentifier), и валидира дека рецептот постои со _context.Recipes.FindAsync(). |
| 74 | 4. _context.FavoriteRecipes.FirstOrDefaultAsync() проверува за постоечки запис. Ако постои, го брише со _context.Remove(), инаку креира нов FavoriteRecipe запис и го додава со _context.Add(). |
| 75 | 5. Контролерот враќа isFavorited boolean flag што укажува на финалната состојба, дозволувајќи кодот на клиентска страна да се синхронизира со серверската состојба независно од почетната вредност. |
| 76 | 6. showToast() прикажува success/error пораки базирани на data.message од server response, давајќи визуелен feedback за акцијата ("Added to favorites" / "Removed from favorites"). |
| 77 | |
| 78 | == ID: 10 |
| 79 | 1. toggleFavoritesFilter() се повикува кога корисникот кликнува на копчето Show Favorites Only. |
| 80 | 2. Се итерираат сите .recipe-card елементи и се проверува нивниот data-favorited атрибут - ако е "true" се прикажуваат, инаку се сокриваат. |
| 81 | 3. visibleCount се пресметува и ажурира, а текстот на копчето се менува од "Show Favorites Only" во "Show All Recipes". |
| 82 | 4. При повторно кликање, сите recipe cards повторно се прикажуваат и состојбата се враќа на почетната. |
| 83 | |
| 84 | == ID: 12 |
| 85 | 1. User.FindFirstValue(ClaimTypes.NameIdentifier) се користи за да се земе тековниот корисник, а _context.Recipes.Where(r => r.UserId == userId) ги филтрира само рецептите на тој корисник. |
| 86 | 2. recipe.Ratings.Average(r => r.Rating) пресметува просечен рејтинг за секој рецепт, а ако нема рејтинзи се поставува на 0. |
| 87 | 3. _context.RecipeRatings.Where().GroupBy() ги групира рејтинзите по рецепт, а Math.Round(averageRating / ratings.Count(), 1) го пресметува вкупниот просек на корисникот. |
| 88 | 4. Razor логиката @if (recipe.RecipeStatus == "Pending/Accepted/Declined") рендерира соодветен badge со Bootstrap класи и FontAwesome икони за тоа дали рецептот е во исчекување, прифатен или одбиен. |
| 89 | |
| 90 | == ID: 13 |
| 91 | 1. _context.Recipes.Where(r => r.RecipeStatus == "Pending") ги филтрира само рецептите со статус "Pending". |
| 92 | 2. Razor кодот @Model.Count Pending го прикажува бројот на непотврдени рецепти, а секој recipe card добива checkbox за bulk операции. |
| 93 | 3. setupBulkActions() во DOMContentLoaded поставува event listeners на selectAll checkbox и сите .recipe-checkbox елементи за групно селектирање. |
| 94 | 4. setupSearchFunctionality() и setupSortingFunctionality() овозможуваат филтрирање по наслов/автор и сортирање по датум, автор или калории преку DOM манипулација. |
| 95 | 5. showRecipeDetails(recipeId, true, 'Buttons') праќа isOwner=true и recipeControl='Buttons' параметри за да се прикажат approve/decline копчиња во modal-от. |
| 96 | 7. removeRecipeCard() ја отстранува одобрената/одбиената recipe card од DOM-от, updateDisplayCount() го ажурира бројачот. |
| 97 | |
| 98 | == ID: 14 |
| 99 | 1. approveRecipe(recipeId) испраќа POST request со JSON body {recipeId} до /Admin/ApproveRecipe акцијата. |
| 100 | 2. request.TryGetProperty("recipeId") го извлекува ID-то, а _context.Recipes.Include().FirstOrDefaultAsync() го наоѓа рецептот. |
| 101 | 3. recipe.RecipeStatus = "Accepted" го менува статусот, додека ingredient.Status = null ги одобрува pending состојките и recipe.HasPendingIngredients = false го ресетира flag-от. |
| 102 | 4. _context.SaveChangesAsync() ги зачувува промените, а контролерот враќа JSON response со {success: true, message}. |
| 103 | 5. removeRecipeCard(recipeId) ја отстранува recipe card од DOM-от со fade-out анимација, hideRecipeDetails() го затвора modal-от, и showSuccess() прикажува toast порака. |
| 104 | |
| 105 | == ID: 15 |
| 106 | 1. declineRecipe(recipeId) повикува fetch(/Admin/DeclineReasonModel/${recipeId}) за да го вчита partial view-то _RecipeDeclineAdminPartial, го сокрива главниот recipe modal и го прикажува decline modal-от. |
| 107 | 2. fetch('/Admin/DeclineRecipe') испраќа POST request со JSON body содржејќи recipeId, reason и notes, користејќи RequestVerificationToken за безбедност. |
| 108 | 3. /Admin/DeclineRecipe акцијата ги извлекува параметрите со request.TryGetProperty(), ги поставува recipe.RecipeStatus = "Declined", recipe.DeclineReason и recipe.AdminComment полињата. |
| 109 | 4. При успешен одговор, bootstrap.Modal.getInstance().hide() го затвора modal-от, showSuccess() прикажува порака, и removeRecipeCard(recipeId) ја отстранува recipe card од DOM-то. |
| 110 | |
| 111 | == ID: 16 |
| 112 | 1. JavaScript addEventListener('input') на DailyCalories полето автоматски ги пресметува протеини, јаглехидрати и масти со формулите proteinCals / 4, carbCals / 4, и fatCals / 9 базирано на 30-40-30% дистрибуција. |
| 113 | 2. GenerateWeeklyMealPlanAsync() креира нов WeeklyMealPlan објект со GeneratedAt = DateTime.UtcNow и иницијализира празна MealSlots листа. |
| 114 | 3. DistributeRestaurantMeals() создава HashSet<string> од достапни слотови во формат "ден_тип_оброк", а потоа рандом селектира Math.Min(totalRestaurantMeals, availableSlots.Count) слотови кои ќе бидат одвоени за оброци од ресторан. |
| 115 | 4. Надворешен foreach за days низата и внатрешен foreach за mealTypes, каде што секоја итерација креира нов MealSlot објект со соодветни Day и MealType вредности. |
| 116 | 5. CalculateMealMacros() користи _mealTypeDistribution Dictionary (breakfast: 25%, lunch: 35%, dinner: 35%, snack: 5%) за да ги распредели дневните макроси на поединечните оброци во денот. |
| 117 | 6. SelectRecipeAsync() користи _context.Recipes.Include(r => r.RecipeIngredients).Where(r => r.RecipeStatus == "Accepted") за да најде одобрени рецепти, со дополнителен .Where(r => r.Type.Contains(mealType)) филтер за да се обезбеди валидноста во однос на типот на оброк. |
| 118 | 7. CalculateMacroMatchScore() пресметува score базиран на Math.Abs(recipe.Calories - targetMacros.Calories) за секоја макро вредност, со тежински фактори (calories: 40%, protein/carbs/fat: 20%). |
| 119 | 8. .OrderByDescending(x => x.Score).Take(10) ги зема најдобрите 10 рецепти, а потоа _random.Next(Math.Min(10, scoredRecipes.Count)) селектира некој рандом од нив. SelectRestaurantMealAsync() работи аналогно како SelectRecipeAsync(), но операра врз _context.RestaurantMeals табелата и користи ист macro matching алгоритам. |
| 120 | 9. CalculateRemainingCalories() ги сумира калориите од сите оброци за денот, а ако остануваат повеќе од 100 калории, се додава ужина со пропорции 15% протеин, 50% јаглехидрати, 35% масти. |
| 121 | 10. CalculateDailyMacroTotals() итерира низ секој ден и ги сумира макросите од сите оброци користејќи тернарен оператор за проверка ms.IsRestaurantMeal ? ms.RestaurantMeal?.Calories : ms.Recipe?.Calories. |
| 122 | 11. При успех, TempData["Success"] ја поставува пораката за следниот request, а RedirectToAction("Details", new { id = result.WeeklyMealPlan.Id }) прави redirect кон детали страницата на генерираното мени. |