This commit is contained in:
@@ -434,52 +434,6 @@ namespace Aberwyn.Controllers
|
||||
}
|
||||
|
||||
|
||||
// POST: api/budget/copy/byname/{targetName}?from={sourceName}
|
||||
[HttpPost("copy/byname/{targetName}")]
|
||||
public async Task<IActionResult> CopyFromNamedBudget(string targetName, [FromQuery] string from)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == targetName.ToLower());
|
||||
|
||||
if (targetPeriod != null && targetPeriod.Categories.Any())
|
||||
return BadRequest("Det finns redan data för denna budget.");
|
||||
|
||||
var sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == from.ToLower());
|
||||
|
||||
if (sourcePeriod == null)
|
||||
return NotFound("Ingen budget hittades att kopiera från.");
|
||||
|
||||
var newPeriod = new BudgetPeriod
|
||||
{
|
||||
Name = targetName,
|
||||
Categories = sourcePeriod.Categories.Select(cat => new BudgetCategory
|
||||
{
|
||||
Name = cat.Name,
|
||||
Color = cat.Color,
|
||||
Order = cat.Order,
|
||||
BudgetCategoryDefinitionId = cat.BudgetCategoryDefinitionId,
|
||||
Items = cat.Items.Select(item => new BudgetItem
|
||||
{
|
||||
Name = item.Name,
|
||||
Amount = item.Amount,
|
||||
IsExpense = item.IsExpense,
|
||||
IncludeInSummary = item.IncludeInSummary,
|
||||
Order = item.Order,
|
||||
BudgetItemDefinitionId = item.BudgetItemDefinitionId
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return Ok(new { id = newPeriod.Id });
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete("item/{id}")]
|
||||
@@ -629,5 +583,131 @@ namespace Aberwyn.Controllers
|
||||
|
||||
|
||||
|
||||
|
||||
// Gemensam intern metod
|
||||
private async Task<BudgetPeriod?> CopyBudgetAsync(
|
||||
BudgetPeriod targetPeriod,
|
||||
BudgetPeriod sourcePeriod)
|
||||
{
|
||||
if (sourcePeriod == null) return null;
|
||||
|
||||
targetPeriod.Categories = sourcePeriod.Categories.Select(cat => new BudgetCategory
|
||||
{
|
||||
Name = cat.Name,
|
||||
Color = cat.Color,
|
||||
Order = cat.Order,
|
||||
BudgetCategoryDefinitionId = cat.BudgetCategoryDefinitionId,
|
||||
Items = cat.Items.Select(item => new BudgetItem
|
||||
{
|
||||
Name = item.Name,
|
||||
Amount = item.Amount,
|
||||
IsExpense = item.IsExpense,
|
||||
IncludeInSummary = item.IncludeInSummary,
|
||||
Order = item.Order,
|
||||
BudgetItemDefinitionId = item.BudgetItemDefinitionId
|
||||
}).ToList()
|
||||
}).ToList();
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return targetPeriod;
|
||||
}
|
||||
[HttpPost("copy/byname/{targetName}")]
|
||||
public async Task<IActionResult> CopyToNamedBudget(
|
||||
string targetName,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] int? fromYear,
|
||||
[FromQuery] int? fromMonth)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == targetName.ToLower());
|
||||
|
||||
if (targetPeriod == null)
|
||||
{
|
||||
targetPeriod = new BudgetPeriod { Name = targetName };
|
||||
_context.BudgetPeriods.Add(targetPeriod);
|
||||
}
|
||||
else if (targetPeriod.Categories.Any())
|
||||
{
|
||||
return BadRequest("Det finns redan data för denna budget.");
|
||||
}
|
||||
|
||||
// Hämta källperiod
|
||||
var sourcePeriod = await FindSourcePeriod(from, fromYear, fromMonth);
|
||||
if (sourcePeriod == null)
|
||||
return NotFound("Ingen budget hittades att kopiera från.");
|
||||
|
||||
await CopyBudgetAsync(targetPeriod, sourcePeriod);
|
||||
return Ok(new { id = targetPeriod.Id });
|
||||
}
|
||||
[HttpPost("copy/{year:int}/{month:int}")]
|
||||
public async Task<IActionResult> CopyToYearMonth(
|
||||
int year,
|
||||
int month,
|
||||
[FromQuery] string? from,
|
||||
[FromQuery] int? fromYear,
|
||||
[FromQuery] int? fromMonth)
|
||||
{
|
||||
var targetPeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == year && p.Month == month);
|
||||
|
||||
if (targetPeriod == null)
|
||||
{
|
||||
targetPeriod = new BudgetPeriod { Year = year, Month = month };
|
||||
_context.BudgetPeriods.Add(targetPeriod);
|
||||
}
|
||||
else if (targetPeriod.Categories.Any())
|
||||
{
|
||||
return BadRequest("Det finns redan data för denna månad.");
|
||||
}
|
||||
|
||||
// Hämta källperiod
|
||||
var sourcePeriod = await FindSourcePeriod(from, fromYear, fromMonth, year, month);
|
||||
if (sourcePeriod == null)
|
||||
return NotFound("Ingen data att kopiera från.");
|
||||
|
||||
await CopyBudgetAsync(targetPeriod, sourcePeriod);
|
||||
return Ok(new { id = targetPeriod.Id });
|
||||
}
|
||||
private async Task<BudgetPeriod?> FindSourcePeriod(
|
||||
string? from,
|
||||
int? fromYear,
|
||||
int? fromMonth,
|
||||
int? defaultYear = null,
|
||||
int? defaultMonth = null)
|
||||
{
|
||||
BudgetPeriod? sourcePeriod = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(from))
|
||||
{
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == from.ToLower());
|
||||
}
|
||||
else if (fromYear.HasValue && fromMonth.HasValue)
|
||||
{
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == fromYear && p.Month == fromMonth);
|
||||
}
|
||||
else if (defaultYear.HasValue && defaultMonth.HasValue)
|
||||
{
|
||||
var previous = new DateTime(defaultYear.Value, defaultMonth.Value, 1).AddMonths(-1);
|
||||
sourcePeriod = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Year == previous.Year && p.Month == previous.Month);
|
||||
}
|
||||
|
||||
return sourcePeriod;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,8 @@
|
||||
<a href="/budget/list" class="nav-button" style="margin-right: 10px;">
|
||||
Lista
|
||||
</a>
|
||||
<span class="month-label">
|
||||
{{ budget.name }}
|
||||
</span>
|
||||
<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)">
|
||||
@@ -203,20 +202,61 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
||||
<p>Det finns ingen budgetdata för
|
||||
<strong>{{ budget.name || (selectedMonth + '/' + selectedYear) }}</strong>.
|
||||
|
||||
<!-- 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>
|
||||
<button ng-click="copyPreviousMonthSafe()">
|
||||
</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">
|
||||
@@ -283,7 +323,8 @@
|
||||
<script>
|
||||
window.initialYear = @(ViewBag.Year ?? "null");
|
||||
window.initialMonth = @(ViewBag.Month ?? "null");
|
||||
window.initialName = "@(ViewBag.BudgetName ?? "")";
|
||||
window.initialName = "@Html.Raw(ViewBag.BudgetName ?? "")";
|
||||
|
||||
|
||||
</script>
|
||||
<script src="~/js/budget.js"></script>
|
||||
|
||||
@@ -141,21 +141,4 @@
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Mobilanpassning */
|
||||
@media (max-width: 768px) {
|
||||
.budget-card {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.month-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.month-bars {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,3 +780,23 @@ body.dark-mode {
|
||||
[ng-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.loading = true;
|
||||
$scope.error = null;
|
||||
$scope.menuOpen = false;
|
||||
|
||||
$scope.chartMode = "pie";
|
||||
const initialName = window.initialName;
|
||||
|
||||
@@ -337,37 +338,125 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
});
|
||||
}
|
||||
};
|
||||
$scope.selectedBudgetToCopy = null;
|
||||
$scope.formatBudgetName = function (b) {
|
||||
if (b.name) return b.name; // Namnbudget
|
||||
if (b.year && b.month) {
|
||||
// Visa t.ex. "Mars 2025"
|
||||
const monthNames = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
||||
return monthNames[b.month - 1] + " " + b.year;
|
||||
}
|
||||
return "Okänd budget";
|
||||
};
|
||||
|
||||
$scope.showCopyModal = false; // styr om modalen syns
|
||||
$scope.budgetList = []; // lista över alla budgeter
|
||||
$scope.selectedBudgetToCopy = null;
|
||||
|
||||
$scope.copyBudget = function () {
|
||||
$scope.loadBudgetList(); // fyller $scope.budgetList
|
||||
$scope.selectedBudgetToCopy = null;
|
||||
$scope.showCopyModal = true;
|
||||
};
|
||||
|
||||
|
||||
|
||||
$scope.confirmCopyBudget = function () {
|
||||
if (!$scope.selectedBudgetToCopy) {
|
||||
$scope.showToast("Välj en budget att kopiera från", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Förhindra att kopiera till sig själv
|
||||
if ($scope.budget.name && $scope.selectedBudgetToCopy.name &&
|
||||
$scope.budget.name === $scope.selectedBudgetToCopy.name) {
|
||||
$scope.showToast("Du kan inte kopiera en budget till sig själv", true);
|
||||
return;
|
||||
}
|
||||
if (!$scope.budget.name && $scope.selectedBudgetToCopy.year &&
|
||||
$scope.selectedBudgetToCopy.month &&
|
||||
$scope.selectedYear === $scope.selectedBudgetToCopy.year &&
|
||||
$scope.selectedMonth === $scope.selectedBudgetToCopy.month) {
|
||||
$scope.showToast("Du kan inte kopiera en budget till sig själv", true);
|
||||
return;
|
||||
}
|
||||
|
||||
let url;
|
||||
if ($scope.budget.name) {
|
||||
const from = prompt("Ange namnet på budgeten du vill kopiera ifrån:");
|
||||
if (!from) return;
|
||||
|
||||
$http.post(`/api/budget/copy/byname/${encodeURIComponent($scope.budget.name)}?from=${encodeURIComponent(from)}`)
|
||||
.then(() => {
|
||||
$scope.showToast("Budget kopierad!");
|
||||
$scope.loadBudget();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Kunde inte kopiera budget:", err);
|
||||
$scope.showToast("Fel vid kopiering", true);
|
||||
});
|
||||
// Aktiv budget = namnbudget
|
||||
url = `/api/budget/copy/byname/${encodeURIComponent($scope.budget.name)}`;
|
||||
if ($scope.selectedBudgetToCopy.name) {
|
||||
url += `?from=${encodeURIComponent($scope.selectedBudgetToCopy.name)}`;
|
||||
} else {
|
||||
url += `?fromYear=${$scope.selectedBudgetToCopy.year}&fromMonth=${$scope.selectedBudgetToCopy.month}`;
|
||||
}
|
||||
} else {
|
||||
if (!confirm("Vill du kopiera föregående månad till den aktuella?")) return;
|
||||
|
||||
// Aktiv budget = månad/år
|
||||
const year = $scope.selectedYear;
|
||||
const month = $scope.selectedMonth;
|
||||
|
||||
$http.post(`/api/budget/copy/${year}/${month}`)
|
||||
.then(() => {
|
||||
$scope.showToast("Föregående månad kopierad!");
|
||||
$scope.loadBudget();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Kunde inte kopiera månad:", err);
|
||||
$scope.showToast("Fel vid kopiering av månad", true);
|
||||
});
|
||||
url = `/api/budget/copy/${year}/${month}`;
|
||||
if ($scope.selectedBudgetToCopy.name) {
|
||||
url += `?from=${encodeURIComponent($scope.selectedBudgetToCopy.name)}`;
|
||||
} else {
|
||||
url += `?fromYear=${$scope.selectedBudgetToCopy.year}&fromMonth=${$scope.selectedBudgetToCopy.month}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Postar till URL:", url);
|
||||
|
||||
$http.post(url)
|
||||
.then(() => {
|
||||
$scope.showToast("Budget kopierad!");
|
||||
$scope.loadBudget();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Kunde inte kopiera budget:", err);
|
||||
$scope.showToast("Fel vid kopiering", true);
|
||||
})
|
||||
.finally(() => {
|
||||
$scope.showCopyModal = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$scope.copyExistingBudget = function () {
|
||||
const from = prompt("Ange namnet på budgeten du vill kopiera från:");
|
||||
if (!from) return;
|
||||
|
||||
let url;
|
||||
if ($scope.budget.name) {
|
||||
// Om det är en namnbudget
|
||||
url = `/api/budget/copy/byname/${encodeURIComponent($scope.budget.name)}?from=${encodeURIComponent(from)}`;
|
||||
} else {
|
||||
// Om det är en månadsbudget
|
||||
const year = $scope.selectedYear;
|
||||
const month = $scope.selectedMonth;
|
||||
url = `/api/budget/copy/${year}/${month}?from=${encodeURIComponent(from)}`;
|
||||
}
|
||||
|
||||
$http.post(url)
|
||||
.then(() => {
|
||||
$scope.showToast("Budget kopierad!");
|
||||
$scope.loadBudget();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Fel vid kopiering av budget:", err);
|
||||
if (err.status === 404) {
|
||||
$scope.showToast("Budgeten du försökte kopiera från finns inte.", true);
|
||||
} else if (err.status === 400) {
|
||||
$scope.showToast("Budgeten har redan data.", true);
|
||||
} else {
|
||||
$scope.showToast("Kunde inte kopiera budget.", true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.createNamedBudget = function () {
|
||||
const name = prompt("Ange namn på din nya budget:");
|
||||
if (!name) return;
|
||||
@@ -920,27 +1009,36 @@ $scope.addItemFromDefinition = function (cat) {
|
||||
};
|
||||
|
||||
$scope.createEmptyBudget = function () {
|
||||
if (!$scope.budget || !$scope.budget.name) {
|
||||
$scope.showToast("Ogiltigt budgetnamn.");
|
||||
if (!$scope.budget) {
|
||||
$scope.showToast("Ogiltig budget.");
|
||||
return;
|
||||
}
|
||||
|
||||
const dto = {
|
||||
name: $scope.budget.name
|
||||
};
|
||||
let payload = {};
|
||||
|
||||
$http.post('/api/budget', dto)
|
||||
if ($scope.budget.name) {
|
||||
// Namnbudget
|
||||
payload.name = $scope.budget.name;
|
||||
} else {
|
||||
// Månad/år-budget
|
||||
payload.year = $scope.selectedYear;
|
||||
payload.month = $scope.selectedMonth;
|
||||
}
|
||||
|
||||
$http.post('/api/budget', payload)
|
||||
.then(() => {
|
||||
$scope.showToast("Ny budget skapad.");
|
||||
$scope.loadBudget(); // ladda om efter skapandet
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fel vid skapande:", error);
|
||||
$scope.showToast("Kunde inte skapa budget.");
|
||||
$scope.showToast("Kunde inte skapa budget.", true);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
$scope.drawCategoryChart = function () {
|
||||
const ctx = document.getElementById("expenseChart");
|
||||
if (!ctx || !$scope.budget?.categories) return;
|
||||
@@ -1053,6 +1151,19 @@ $scope.addItemFromDefinition = function (cat) {
|
||||
$scope.importPreview = [];
|
||||
};
|
||||
|
||||
$scope.budgetList = [];
|
||||
|
||||
$scope.loadBudgetList = function () {
|
||||
return $http.get('/api/budget/list') // eller den endpoint du använder
|
||||
.then(res => {
|
||||
$scope.budgetList = res.data;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Kunde inte hämta budgetlista:", err);
|
||||
$scope.showToast("Fel vid hämtning av budgetlista", true);
|
||||
});
|
||||
};
|
||||
$scope.loadBudgetList();
|
||||
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
Reference in New Issue
Block a user