Files
Aberwyn/Aberwyn/Views/Budget/Index.cshtml
Elias Jansson 5a17df917d
All checks were successful
continuous-integration/drone/push Build is passing
Budget report!
2025-09-21 14:40:00 +02:00

337 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@attribute [Authorize(Roles = "Budget")]
@using Microsoft.AspNetCore.Authorization
@{
ViewData["Title"] = "Budget";
}
<div ng-app="budgetApp" ng-controller="BudgetController">
<div class="budget-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
<div class="month-nav-bar" ng-if="!budget.name" style="display: flex; align-items: center; gap: 10px; position: relative;">
<a href="/budget/list" class="nav-button" style="margin-right: 10px;">
Lista
</a>
<button class="nav-button" ng-click="previousMonth()">←</button>
<span class="month-label" ng-click="showMonthPicker = !showMonthPicker" style="cursor: pointer;">
{{ selectedMonthName }} {{ selectedYear }}
</span>
<button class="nav-button" ng-click="nextMonth()">→</button>
<div class="month-picker-dropdown" ng-show="showMonthPicker" style="position: absolute; top: 100%; left: 50%; transform: translateX(-50%); background: white; border: 1px solid #ccc; padding: 10px; border-radius: 8px; z-index: 1000; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
<select ng-model="tempMonth" ng-options="month for month in monthNames" style="margin-bottom: 8px;"></select><br>
<input type="number" ng-model="tempYear" placeholder="År" style="width: 100%; margin-bottom: 8px;" />
<button class="nav-button" ng-click="applyMonthSelection()">Välj</button>
</div>
</div>
<div class="month-nav-bar" ng-if="budget.name">
<a href="/budget/list" class="nav-button" style="margin-right: 10px;">
Lista
</a>
<span class="month-label" ng-bind="budget.name"></span>
</div>
<div class="menu-container" ng-class="{ 'open': menuOpen }">
<button class="icon-button" ng-click="toggleMenu($event)">
<i class="fa fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu" ng-show="menuOpen">
<button class="nav-button" ng-click="createNamedBudget()">Ny budget</button>
<button ng-click="copyBudget(); menuOpen = false;">Kopiera</button>
<button ng-click="deleteBudget(); menuOpen = false;" class="danger">Ta bort</button>
<button ng-click="createNewCategory(); menuOpen = false;">Lägg till ny kategori</button>
<button ng-click="openImportModule(); menuOpen = false;">Importera text</button>
</div>
</div>
</div>
<div class="budget-overview-row" ng-if="budget && budget.categories.length > 0">
<!-- Vänster: Summering -->
<div class="budget-summary-box compact">
<h3>Sammanställning</h3>
<ul class="summary-list">
<li><span>💰 Inkomst:</span> {{ getTotalIncome() | number:0 }} kr</li>
<li><span>💸 Utgift:</span> {{ getTotalExpense() | number:0 }} kr</li>
<li><span>🏦 Sparande:</span> {{ getTotalSaving() | number:0 }} kr</li>
<p>
📈 Kvar:
<span class="highlight leftover" ng-class="{'plus': getLeftover() >= 0, 'minus': getLeftover() < 0}">
{{ getLeftover() | number:0 }} kr
</span>
</p>
</ul>
<hr>
<ul class="category-summary-list">
<li ng-repeat="cat in budget.categories">
<span style="color: {{ cat.color }}">{{ cat.name }}</span>
<span>{{ getCategorySum(cat) | number:0 }} kr</span>
</li>
</ul>
</div>
<!-- Höger: Diagramväxling -->
<div class="budget-chart-box compact">
<div class="chart-row">
<div class="chart-legend">
<h4>Utgifter</h4>
<p style="font-size: 12px; margin: 4px 0 10px;">
Totalt: <strong>{{ getTotalExpense() | number:0 }} kr</strong>
</p>
<p ng-if="topExpenseCategory" style="font-size: 12px; margin-bottom: 12px;">
Största kategori: <br>
<strong>{{ topExpenseCategory.name }}</strong> ({{ topExpenseCategory.percent | number:1 }}%)
</p>
</div>
<div class="chart-area">
<canvas id="expenseChart"></canvas>
</div>
</div>
</div>
</div>
<div class="budget-grid" ng-if="budget && budget.categories">
<div class="budget-card"
ng-repeat="cat in budget.categories"
style="--cat-color: {{cat.color}};"
draggable-category
category="cat"
on-category-drop="handleCategoryDrop(data, targetCategory)">
<div class="card-header" style="background-color: {{cat.color}};">
<div class="header-left" ng-if="!cat.editing" >{{ cat.name }}</div>
<input class="header-edit" type="text" ng-model="cat.name" ng-if="cat.editing" />
<input class="color-edit" type="color" ng-model="cat.color" ng-if="cat.editing" />
<div class="header-actions">
<button class="icon-button" ng-click="cat.editing ? saveCategory(cat) : cat.editing = true">
<i ng-class="{'fa fa-check': cat.editing, 'fa fa-pencil': !cat.editing}"></i>
</button>
<button class="icon-button" ng-if="cat.editing" ng-click="cancelCategoryEdit(cat)">
<i class="fa fa-times"></i>
</button>
<button class="icon-button" ng-if="cat.editing" ng-click="cat.allowDrag = !cat.allowDrag" title="Flytt av kategori">
<i class="fa" ng-class="cat.allowDrag ? 'fa-unlock' : 'fa-lock'"></i>
</button>
<button class="icon-button danger" ng-if="cat.editing" ng-click="deleteCategory(cat)">
<i class="fa fa-trash"></i>
</button>
</div>
</div>
<div class="item-list">
<div ng-repeat="item in cat.items track by item.id">
<div drop-placeholder
category="cat"
index="$index"
on-drop-item="handleItemPreciseDrop(data, cat, $index)">
</div>
<div class="item-row"
draggable-item
item="item"
category="cat"
on-item-drop="handleItemDrop(event, data, cat)"
ng-class="{
dragging: dragInProgress && draggedItemId === item.id,
due: item.paymentStatus === 1,
paid: item.paymentStatus === 2
}"
ng-click="handleItemInteraction($event, item)">
<i class="fa fa-grip-lines drag-handle"
ng-show="cat.editing"
style="opacity: 0.5; padding-right: 6px; cursor: grab;"></i>
<input type="text" class="item-label" ng-model="item.name" ng-if="cat.editing" />
<span ng-if="!cat.editing" class="item-label" title="{{ item.name }}">{{ item.name }}</span>
<!-- debug
<span ng-if="!cat.editing" class="item-definition" title="{{ item.definitionName }}">#{{ item.budgetItemDefinitionId }} {{ item.definitionName }}</span>
<input type="text" ng-model="item.budgetItemDefinitionId" ng-if="cat.editing" />
-->
<input type="number" ng-model="item.amount" ng-if="cat.editing" />
<span class="amount" ng-if="!cat.editing">{{ item.amount | number:0 }}</span>
<!-- 3-pricksmeny -->
<div class="item-menu-container" ng-if="cat.editing" style="position: relative;">
<button class="icon-button" ng-click="openItemMenu($event, item)">
<i class="fa fa-ellipsis-v"></i>
</button>
<div class="item-floating-menu" ng-show="menuVisible" ng-style="menuStyle">
<span style="font-size: 11px; color: #94a3b8; display: block; margin: 0px 0px 0px 40px;">
#{{ menuItem.definitionName }}
</span>
<button ng-click="deleteItem(menuItem.category, menuItem)">🗑 Ta bort</button>
<hr>
<button
ng-click="setItemType(menuItem, 'expense')"
ng-class="{ 'disabled': menuItem.isExpense }">
<span ng-if="menuItem.isExpense">✔️</span> 💸 Utgift
</button>
<button
ng-click="setItemType(menuItem, 'income')"
ng-class="{ 'disabled': !menuItem.isExpense && menuItem.includeInSummary }">
<span ng-if="!menuItem.isExpense && menuItem.includeInSummary">✔️</span> 💰 Inkomst
</button>
<button
ng-click="setItemType(menuItem, 'saving')"
ng-class="{ 'disabled': !menuItem.isExpense && !menuItem.includeInSummary }">
<span ng-if="!menuItem.isExpense && !menuItem.includeInSummary">✔️</span> 🏦 Sparande
</button>
</div>
</div>
</div>
</div>
<div drop-fallback
ng-if="cat.editing"
category="cat"
on-drop-item="handleItemPreciseDrop(data, targetCategory, targetIndex)">
</div>
<button class="icon-button add-post-btn"
ng-if="cat.editing && !cat.addingItem"
ng-click="openItemPopup($event, cat)">
Lägg till post
</button>
</div>
<div class="item-row total-row">
<div>Summa</div>
<div class="amount">{{ getCategorySum(cat) | number:0 }}</div>
</div>
</div>
</div>
<!-- Ingen budget alls -->
<div class="no-data" ng-if="!loading && (!budget || !budget.id)">
<p>
<strong ng-bind="budget && budget.name
? 'Budgeten \" ' + budget.name + ' \" finns inte.'
: 'Det finns ingen budget för ' + getMonthName(selectedMonth) + ' ' + selectedYear + '.' ">
</strong>
</p>
<div style="margin-top: 10px;">
<!-- Skapa ny budget alltid om budget saknas -->
<button ng-click="createEmptyBudget()" style="margin-right: 10px;">
Skapa ny budget
</button>
</div>
</div>
<!-- Budget finns men inga kategorier -->
<div class="no-data" ng-if="!loading && budget && budget.id && (!budget.categories || budget.categories.length === 0)">
<p>
Budgeten <strong>{{ budget.name || (getMonthName(selectedMonth) + " " + selectedYear) }}</strong> har inga kategorier än.
</p>
<div style="margin-top: 10px;">
<button ng-click="createNewCategory()" style="margin-right: 10px;">
Skapa ny kategori
</button>
<button ng-if="!budget.name" ng-click="copyPreviousMonthSafe()" style="margin-right: 10px;">
Kopiera föregående månad
</button>
<button ng-click="copyBudget()" style="margin-right: 10px;">
Kopiera befintlig budget
</button>
</div>
</div>
<!-- Modal -->
<div class="modal-backdrop" ng-show="showCopyModal">
<div class="modal-content">
<h3>Kopiera budget</h3>
<select ng-model="selectedBudgetToCopy"
ng-options="b as formatBudgetName(b) for b in budgetList track by b.id">
<option value="">Välj budget</option>
</select>
<div style="margin-top: 10px;">
<button ng-click="confirmCopyBudget()">Kopiera</button>
<button ng-click="showCopyModal = false">Avbryt</button>
</div>
</div>
</div>
<div class="add-item-popup" ng-show="addPopupVisible" ng-style="addPopupStyle" ng-class="{ 'above': addPopupAbove }">
<label>Typ:</label>
<select ng-model="addPopupData.newItemType">
<option value="expense">💸 Utgift</option>
<option value="income">💰 Inkomst</option>
<option value="saving">🏦 Sparande</option>
</select>
<label>Definition:</label>
<input type="text"
ng-model="addPopupData.newItemDefinition"
ng-change="updateDefinitionSuggestions()"
placeholder="Ex: Elhandel"
autocomplete="on"
ng-blur="hideSuggestionsDelayed()"
ng-focus="showDefinitionSuggestions = true" />
<p style="color: red">{{ filteredDefinitions.length }} träffar</p>
<ul class="suggestion-list" ng-show="showDefinitionSuggestions && filteredDefinitions.length > 0">
<li ng-repeat="suggestion in filteredDefinitions"
ng-mousedown="selectDefinitionSuggestion(suggestion.Name)">
{{ suggestion.Name }}
</li>
</ul>
<label>Etikett (valfritt):</label>
<input type="text" ng-model="addPopupData.newItemLabel" placeholder="Ex: Däck till Volvon" />
<label>Belopp:</label>
<input type="number" ng-model="addPopupData.newItemAmount" placeholder="0" />
<button ng-click="addItemFromPopup()">Lägg till</button>
<button ng-click="addPopupVisible = false">Avbryt</button>
</div>
<div class="import-module" ng-show="importing" style="position: fixed; top: 10vh; left: 50%; transform: translateX(-50%);
background: #1F2C3C; color: white; padding: 24px; border-radius: 8px; z-index: 2000; width: 90%; max-width: 600px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);">
<h3 style="margin-top: 0;">📥 Importera rader till {{ importTargetCategory.name }}</h3>
<label for="importText">Klistra in rader (en per rad, t.ex. <code>Kallhyra 11315</code>):</label>
<textarea id="importText" ng-model="importText" rows="6" style="width: 100%; margin-bottom: 10px;"></textarea>
<button ng-click="parseImportText()">Förhandsgranska</button>
<button ng-click="cancelImport()">Avbryt</button>
<div class="preview-list" ng-if="importPreview.length > 0" style="margin-top: 16px;">
<h4>Förhandsvisning:</h4>
<div class="item-row" ng-repeat="item in importPreview">
<span class="item-label">{{ item.name }}</span>
<span class="amount">{{ item.amount | number:0 }} kr</span>
</div>
<button ng-click="applyImport()">✔️ Skapa {{ importPreview.length }} rader</button>
</div>
</div>
</div>
<link rel="stylesheet" href="~/css/budget.css" />
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
<script>
window.initialYear = @(ViewBag.Year ?? "null");
window.initialMonth = @(ViewBag.Month ?? "null");
window.initialName = "@Html.Raw(ViewBag.BudgetName ?? "")";
</script>
<script src="~/js/budget.js"></script>
<script src="~/js/budget-dragdrop.js"></script>