Compare commits

..

12 Commits

Author SHA1 Message Date
troogs
701388ee34 Implement sorting functionality for prospects with a dialog and custom sort options 2026-04-16 19:36:32 +02:00
troogs
3e099988bc Add ProspectSortOption enum and implement sorting dialog with buttons 2026-04-16 19:36:26 +02:00
troogs
ee967cd046 Enhance Prospect display in ProspectContainer.razor to show warning for missing name and format FsId 2026-04-16 16:59:04 +02:00
troogs
76c3e6ddde Refactor Button component in Prospects.razor for improved styling and structure 2026-04-16 16:57:39 +02:00
troogs
aea858a1ef Add GitHub Actions workflow for building and pushing Docker images
All checks were successful
Build And Push Docker Image / docker (push) Successful in 1m50s
2026-04-10 21:17:51 +02:00
troogs
85c90e4657 Add DOTNET_ROLL_FORWARD environment variable to build workflow
All checks were successful
Build And Release / release (push) Successful in 1m26s
2026-04-10 20:56:12 +02:00
troogs
840ecedbd0 Add GitHub Actions workflow for build and release process
Some checks failed
Build And Release / release (push) Failing after 1m8s
2026-04-10 20:52:03 +02:00
troogs
916c3142d4 Remove unused Blazorise script reference and add kill-port script for managing listening processes 2026-04-10 05:48:37 +02:00
troogs
f9426679ea Add tasks.json for managing FoodsharingSiegen.Server build and debug tasks 2026-04-10 05:48:29 +02:00
troogs
a68994d00b Add launch configuration for debugging FoodsharingSiegen.Server 2026-04-10 05:48:14 +02:00
troogs
fcda568905 Add app.db to .gitignore to prevent tracking of database file 2026-04-10 05:48:06 +02:00
Andre Beging
ac178e60e0 Update term for ReleasedForVerification interaction type
Changed the term "Freigabe zum Freischalten" to "Freigabe Freischalten" to ensure consistency with naming conventions. This adjustment clarifies language usage and aligns with the application's terminology standards.
2025-04-02 09:26:07 +02:00
15 changed files with 376 additions and 7 deletions

View File

@@ -0,0 +1,56 @@
name: Build And Push Docker Image
on:
push:
tags:
- "image"
workflow_dispatch:
jobs:
docker:
runs-on: ubuntu-latest
env:
DOTNET_ROLL_FORWARD: Major
REGISTRY: git.beging.de
BASE_IMAGE: git.beging.de/troogs/fs-onboarding-server
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: "9.0.x"
- name: Publish server project
run: dotnet publish ./FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj -c Release -o ./Publish/Server
- name: Login to Gitea registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ env.REGISTRY }}" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
- name: Determine next timestamp tag
shell: bash
run: |
TODAY="$(date -u +%Y%m%d)"
i=1
while docker manifest inspect "${BASE_IMAGE}:${TODAY}-${i}" > /dev/null 2>&1; do
i=$((i + 1))
done
DATE_TAG="${TODAY}-${i}"
echo "DATE_TAG=${DATE_TAG}" >> "$GITHUB_ENV"
echo "Using image tag: ${DATE_TAG}"
- name: Build docker image
run: |
docker build \
-f ./Docker/dockerfile.server \
-t "${{ env.BASE_IMAGE }}:latest" \
-t "${{ env.BASE_IMAGE }}:${{ env.DATE_TAG }}" \
.
- name: Push docker images
run: |
docker push "${{ env.BASE_IMAGE }}:latest"
docker push "${{ env.BASE_IMAGE }}:${{ env.DATE_TAG }}"

50
.gitea/workflows/test.yml Normal file
View File

@@ -0,0 +1,50 @@
name: Build And Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag_name:
description: "Tag for the release (for example: v1.0.0)"
required: true
type: string
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.ref_name }}
DOTNET_ROLL_FORWARD: Major
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: "9.0.x"
- name: Restore dependencies
run: dotnet restore FoodsharingSiegen.sln
- name: Publish server build
run: dotnet publish ./FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj -c Release -o ./artifacts/publish
- name: Create zip package
run: |
cd artifacts
zip -r FoodsharingSiegen.Server-${{ env.RELEASE_TAG }}.zip publish
- name: Create release and upload artifact
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: artifacts/FoodsharingSiegen.Server-${{ env.RELEASE_TAG }}.zip
tag_name: ${{ env.RELEASE_TAG }}
name: Release ${{ env.RELEASE_TAG }}
target_commitish: ${{ github.sha }}
token: ${{ github.token }}

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ riderModule.iml
/_ReSharper.Caches/ /_ReSharper.Caches/
Publish/ Publish/
app.db

19
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug FoodsharingSiegen.Server",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj",
"env": {
"DOTNET_ROLL_FORWARD": "Major"
},
"launchBrowser": true,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
}
}
]
}

53
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,53 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Watch FoodsharingSiegen.Server",
"type": "shell",
"command": "dotnet watch --project ./FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj run",
"isBackground": true,
"problemMatcher": "$msCompile",
"options": {
"statusbar": {
"hide": false,
"label": "Watch Server",
"color": "#3a96ff",
"icon": {
"id": "eye"
},
"running": {
"color": "#f7df06",
"icon": {
"id": "loading~spin"
}
}
}
}
},
{
"label": "Debug FoodsharingSiegen.Server",
"type": "shell",
"command": "dotnet run --project ./FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj",
"problemMatcher": "$msCompile",
"options": {
"env": {
"DOTNET_ROLL_FORWARD": "Major"
},
"statusbar": {
"hide": false,
"label": "Debug Server",
"color": "#3a96ff",
"icon": {
"id": "debug-start"
},
"running": {
"color": "#f7df06",
"icon": {
"id": "loading~spin"
}
}
}
}
}
]
}

View File

@@ -0,0 +1,10 @@
namespace FoodsharingSiegen.Contracts.Enums
{
public enum ProspectSortOption
{
NameAscending,
NameDescending,
ModifiedAscending,
ModifiedDescending
}
}

View File

@@ -12,8 +12,23 @@
<div class="@divClass"> <div class="@divClass">
<h5 class="mb-2 d-flex"> <h5 class="mb-2 d-flex">
<div class="flex-grow-1"> <div class="flex-grow-1">
@if(string.IsNullOrWhiteSpace(Prospect?.Name))
{
<i class="fa-solid fa-exclamation-triangle text-warning"></i>
<doublearrows></doublearrows>
<em>»Name fehlt«</em>
}
else
{
@Prospect?.Name @Prospect?.Name
<small style="font-size: .9rem;">@Prospect?.FsId</small> }
@if (Prospect?.FsId != null && Prospect.FsId != 0)
{
<small style="font-size: .9rem; margin-left: 0.5rem;">@Prospect?.FsId</small>
}
</div> </div>
<div> <div>
@if (CurrentUser.IsInGroup(UserGroup.WelcomeTeam, UserGroup.Ambassador)) @if (CurrentUser.IsInGroup(UserGroup.WelcomeTeam, UserGroup.Ambassador))

View File

@@ -0,0 +1,9 @@
@inherits FsBase
@using FoodsharingSiegen.Contracts.Enums
<div class="d-grid gap-2">
<Button Color="@GetSortButtonColor(ProspectSortOption.NameAscending)" Clicked="() => SelectAsync(ProspectSortOption.NameAscending)">Name (aufsteigend)</Button>
<Button Color="@GetSortButtonColor(ProspectSortOption.NameDescending)" Clicked="() => SelectAsync(ProspectSortOption.NameDescending)">Name (absteigend)</Button>
<Button Color="@GetSortButtonColor(ProspectSortOption.ModifiedAscending)" Clicked="() => SelectAsync(ProspectSortOption.ModifiedAscending)">Geändert (aufsteigend)</Button>
<Button Color="@GetSortButtonColor(ProspectSortOption.ModifiedDescending)" Clicked="() => SelectAsync(ProspectSortOption.ModifiedDescending)">Geändert (absteigend)</Button>
</div>

View File

@@ -0,0 +1,39 @@
using Blazorise;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Server.BaseClasses;
using Microsoft.AspNetCore.Components;
namespace FoodsharingSiegen.Server.Dialogs
{
public partial class ProspectSortDialog : FsBase
{
[Parameter]
public ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
[Parameter]
public Func<ProspectSortOption, Task>? OnSortSelected { get; set; }
public static async Task ShowAsync(IModalService modalService, ProspectSortOption currentSort, Func<ProspectSortOption, Task> onSortSelected)
{
await modalService.Show<ProspectSortDialog>("Sortieren", p =>
{
p.Add(nameof(CurrentSort), currentSort);
p.Add(nameof(OnSortSelected), onSortSelected);
}, new ModalInstanceOptions
{
Size = ModalSize.Small,
});
}
private Color GetSortButtonColor(ProspectSortOption option)
{
return CurrentSort == option ? Color.Success : Color.Secondary;
}
private async Task SelectAsync(ProspectSortOption option)
{
if (OnSortSelected != null) await OnSortSelected(option);
await ModalService.Hide();
}
}
}

View File

@@ -16,20 +16,44 @@
<Button <Button
Color="Color.Primary" Color="Color.Primary"
Width="Width.Px(50)"
Height="Height.Px(50)"
title="Hinzufügen"
style="min-width: auto;"
Clicked="@CreateProspectAsync" Clicked="@CreateProspectAsync"
Visibility="@(CurrentUser.IsInGroup(UserGroup.WelcomeTeam, UserGroup.Ambassador) ? Visibility.Default : Visibility.Invisible)" Visibility="@(CurrentUser.IsInGroup(UserGroup.WelcomeTeam, UserGroup.Ambassador) ? Visibility.Default : Visibility.Invisible)"
>Hinzufügen ><i class="fa-solid fa-plus"></i>
</Button> </Button>
<Button
Color="Color.Primary"
Width="Width.Px(50)"
Height="Height.Px(50)"
title="Sortieren"
style="min-width: auto;"
Clicked="@OpenSortDialogAsync"
><i class="fa-solid fa-sort"></i>
</Button>
<div class="badge-row mt-2">
@if (HasCustomSort)
{
<Badge Color="Color.Primary" Closable="true" CloseClicked="@EventCallback.Factory.Create(this, ResetSortAsync)">@CurrentSortText</Badge>
}
</div>
@{ @{
var filterList = ProspectList.ApplyFilter(Filter); var filterList = ProspectList.ApplyFilter(Filter);
var sortList = SortProspects(filterList);
} }
<hr/> <hr/>
<ProspectFilterControl Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.OnBoarding"></ProspectFilterControl> <ProspectFilterControl Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.OnBoarding"></ProspectFilterControl>
<hr /> <hr />
<ProspectGrid <ProspectGrid
Prospects="filterList" Prospects="sortList"
OnDataChanged="@LoadProspects" OnDataChanged="@LoadProspects"
StateFilter="ProspectStateFilter.OnBoarding"> StateFilter="ProspectStateFilter.OnBoarding">
</ProspectGrid> </ProspectGrid>

View File

@@ -36,6 +36,8 @@ namespace FoodsharingSiegen.Server.Pages
/// </summary> /// </summary>
private List<Prospect>? ProspectList { get; set; } private List<Prospect>? ProspectList { get; set; }
private ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
#endregion #endregion
#region Override InitializeDataAsync #region Override InitializeDataAsync
@@ -63,6 +65,32 @@ namespace FoodsharingSiegen.Server.Pages
await EditProspectDialog.ShowAsync(ModalService, LoadProspects); await EditProspectDialog.ShowAsync(ModalService, LoadProspects);
} }
private async Task OpenSortDialogAsync()
{
await ProspectSortDialog.ShowAsync(ModalService, CurrentSort, async option =>
{
CurrentSort = option;
await InvokeAsync(StateHasChanged);
});
}
private bool HasCustomSort => CurrentSort != ProspectSortOption.NameAscending;
private string CurrentSortText => CurrentSort switch
{
ProspectSortOption.NameDescending => "Sortierung: Name (absteigend)",
ProspectSortOption.ModifiedAscending => "Sortierung: Zuletzt geaendert (aufsteigend) ",
ProspectSortOption.ModifiedDescending => "Sortierung: Zuletzt geaendert (absteigend) ",
_ => string.Empty
};
private async Task ResetSortAsync()
{
CurrentSort = ProspectSortOption.NameAscending;
await InvokeAsync(StateHasChanged);
}
#endregion #endregion
#region Private Method FilterChangedAsync #region Private Method FilterChangedAsync
@@ -78,6 +106,18 @@ namespace FoodsharingSiegen.Server.Pages
await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter); await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter);
} }
private List<Prospect> SortProspects(List<Prospect> prospects)
{
return CurrentSort switch
{
ProspectSortOption.NameAscending => prospects.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList(),
ProspectSortOption.NameDescending => prospects.OrderByDescending(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList(),
ProspectSortOption.ModifiedAscending => prospects.OrderBy(x => x.Modified ?? x.Created).ToList(),
ProspectSortOption.ModifiedDescending => prospects.OrderByDescending(x => x.Modified ?? x.Created).ToList(),
_ => prospects
};
}
#endregion #endregion
#region Private Method LoadProspects #region Private Method LoadProspects

View File

@@ -43,7 +43,6 @@
</div> </div>
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
<script src="_content/Blazorise/blazorise.js"></script>
<script src="_content/Blazorise.Material/blazorise.material.js?v=1.7.5.0"></script> <script src="_content/Blazorise.Material/blazorise.material.js?v=1.7.5.0"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1 @@
20260416

View File

@@ -28,7 +28,7 @@ namespace FoodsharingSiegen.Shared.Helper
InteractionType.Verify => "Verifizierung", InteractionType.Verify => "Verifizierung",
InteractionType.Complete => "Fertig", InteractionType.Complete => "Fertig",
InteractionType.StepInBriefing => appSettings.Terms.StepInName ?? "StepIn", InteractionType.StepInBriefing => appSettings.Terms.StepInName ?? "StepIn",
InteractionType.ReleasedForVerification => "Freigabe zum Freischalten", InteractionType.ReleasedForVerification => "Freigabe Freischalten",
_ => type.ToString() _ => type.ToString()
}; };
} }

View File

@@ -0,0 +1,53 @@
param(
[int]$Port = 56000
)
$killedAny = $false
# Try modern cmdlet first.
$connections = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
if ($connections) {
$pids = $connections | Select-Object -ExpandProperty OwningProcess -Unique
foreach ($procId in $pids) {
try {
Stop-Process -Id $procId -Force -ErrorAction Stop
Write-Host "Stopped process $procId listening on port $Port."
$killedAny = $true
}
catch {
Write-Warning "Failed to stop process ${procId}: $($_.Exception.Message)"
}
}
}
# Fallback for environments where Get-NetTCPConnection is unavailable.
if (-not $killedAny) {
$netstatLines = netstat -ano | Select-String ":$Port\s"
$listenLines = $netstatLines | Where-Object { $_.Line -match "LISTENING" }
$fallbackPids = @()
foreach ($line in $listenLines) {
$parts = ($line.Line -replace "\s+", " ").Trim().Split(" ")
if ($parts.Length -ge 5) {
$fallbackPids += $parts[-1]
}
}
$fallbackPids = $fallbackPids | Sort-Object -Unique
foreach ($pidText in $fallbackPids) {
if ($pidText -match "^\d+$") {
try {
Stop-Process -Id ([int]$pidText) -Force -ErrorAction Stop
Write-Host "Stopped process $pidText listening on port $Port."
$killedAny = $true
}
catch {
Write-Warning "Failed to stop process ${pidText}: $($_.Exception.Message)"
}
}
}
}
if (-not $killedAny) {
Write-Host "No listening process found on port $Port."
}