Compare commits

..

11 Commits

Author SHA1 Message Date
456b3f072c Fix: Release.sh 2026-04-13 10:56:51 +02:00
325a06e6b0 BB: Card Styling 2026-04-11 18:18:10 +02:00
f633708b57 New: Working RTE and Card Style View on the index Page 2026-02-24 10:58:53 +01:00
59eb99ab23 New: Description 2026-02-24 10:58:23 +01:00
540b38eada New: Gitignore 2026-02-24 10:56:30 +01:00
7a6fe07d04 Fix: Add ImageID to the migrations. 2026-02-19 19:43:17 +01:00
4b142d4e63 Scaffolding for new Scheduled Job for the BlackBoardDigestJob 2026-02-19 19:42:44 +01:00
2b4e2f84a7 NEW: UI for Description 2026-02-19 19:42:20 +01:00
9c39e97126 NEW: Image Upload 2026-02-19 19:42:08 +01:00
3ddce62f54 New: Interfaces 0.0.0-12 way of doing reporting 2026-02-19 19:40:02 +01:00
1f443b2734 Fix: release.sh make runnable on Linux 2026-02-19 19:34:56 +01:00
14 changed files with 551 additions and 114 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.idea
bin/
debug/
obj/

View File

@@ -0,0 +1,138 @@
@using SZUAbsolventenverein.Module.BlackBoard.Services
@using SZUAbsolventenverein.Module.BlackBoard.Models
@using Interfaces
@namespace SZUAbsolventenverein.Module.BlackBoard
@inherits ModuleBase
@inject IBlackBoardService BlackBoardService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Details> Localizer
@inject IReportUI ReportingComponent
@if (_item == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="row g-0">
<div class="col-lg-5 position-relative d-flex align-items-center justify-content-center p-4" style="min-height: 400px;">
@if (_item.ImageID > 0)
{
<div class="bb-detail-image-bg" style="background-image: url('@ImageUrl(_item.ImageID, 800, 600)');"></div>
<img src="@ImageUrl(_item.ImageID, 800, 600)" class="img-fluid rounded-3 shadow position-relative" style="max-height: 450px; z-index: 1; border: 8px solid white; object-fit: cover;" alt="@_item.Name">
}
else
{
<div class="text-muted position-relative" style="z-index: 1;">
<span style="font-size: 8rem; opacity: 0.2;">📋</span>
</div>
}
</div>
<div class="col-lg-7">
<div class="card-body p-4 p-md-5">
<div class="mb-4">
<h1 class="display-4 fw-bold text-dark mb-0">@_item.Name</h1>
</div>
<hr class="my-4" style="width: 100px; height: 3px; background-color: var(--primary); opacity: 1;">
<div class="bb-description-section mb-5">
<h5 class="text-uppercase fw-bold text-muted mb-3" style="letter-spacing: 1px; font-size: 0.9rem;">Beschreibung</h5>
<div class="lead text-dark mb-4" style="line-height: 1.7; font-size: 1.1rem;">
@((MarkupString)(_item.Description ?? ""))
</div>
</div>
<div class="bb-detail-audit mb-4">
<AuditInfo CreatedBy="@_item.CreatedBy" CreatedOn="@_item.CreatedOn" ModifiedBy="@_item.ModifiedBy" ModifiedOn="@_item.ModifiedOn"></AuditInfo>
</div>
<div class="d-flex flex-wrap gap-3 mt-5">
<NavLink class="btn btn-outline-secondary btn-lg px-4" href="@NavigateUrl()">
<i class="oi oi-arrow-left me-2"></i> Zurück
</NavLink>
<div class="ms-auto d-flex gap-2">
<ActionLink Action="Edit" Parameters="@("id=" + _item.BlackBoardId.ToString())" Class="btn btn-primary btn-lg px-4" Security="SecurityAccessLevel.Edit" Text="Bearbeiten" ResourceKey="Edit" />
<ActionDialog Header="Delete BlackBoard" Message="@Localizer["Message.DeleteConfirm"]" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger btn-lg px-4" ConfirmClass="absolute" OnClick="@(async () => await DeleteEntry())" ResourceKey="Delete" Id="@_item.BlackBoardId.ToString()" />
@if (ReportingComponent != null)
{
<DynamicComponent Type="@ReportingComponent.ReportType" Parameters="@_parameters" />
}
</div>
</div>
</div>
</div>
</div>
}
<style>
.bb-detail-image-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-size: cover;
background-position: center;
filter: blur(30px) brightness(1.1);
opacity: 0.15;
z-index: 0;
}
.bb-details .card {
background: #ffffff;
}
.bb-details .breadcrumb-item a {
text-decoration: none;
color: var(--primary);
}
</style>
@code {
public override string Actions => "Details";
public override List<Resource> Resources => [
new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css")
];
private BlackBoard _item;
private int _id;
private Dictionary<string, object> _parameters = new Dictionary<string, object>();
protected override async Task OnInitializedAsync()
{
try
{
_id = Int32.Parse(PageState.QueryString["id"]);
_item = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId);
if (_item != null && ReportingComponent != null)
{
_parameters = ReportingComponent.ConstructParameterList(_item, RenderModeBoundary);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading BlackBoard {BlackBoardId} {Error}", _id, ex.Message);
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
private async Task DeleteEntry()
{
try
{
await BlackBoardService.DeleteBlackBoardAsync(_item.BlackBoardId, ModuleState.ModuleId);
await logger.LogInformation("BlackBoard Deleted {BlackBoard}", _item);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting BlackBoard {Error}", ex.Message);
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
}
}
}

View File

@@ -7,49 +7,63 @@
@inherits ModuleBase
@inject IBlackBoardService BlackBoardService
@inject NavigationManager NavigationManager
@inject IReportingHandler ReportingHandler
@inject IStringLocalizer<Edit> Localizer
@inject IReportUI ReportingComponent
<form @ref="form" class="@(validated ? " was-validated" : "needs-validation" )" novalidate>
<form @ref="form" class="@(validated ? " was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter a name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" required />
<input id="name" class="form-control" @bind="@_blackBoard.Name" required/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="image" HelpText="Enter a description" ResourceKey="Description">Beschreibung: </Label>
<div class="col-sm-9">
<FileManager AnonymizeUploadFilenames="true" id="image" UploadMultiple="false" ShowSuccess="true" FileId="@_blackBoard.ImageID" OnSelectFile="@OnFileSelected"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="" For="description" HelpText="Enter a description" ResourceKey="Description">Beschreibung: </Label>
<RichTextEditor @ref="RichTextEditorHtml" Content="@_blackBoard.Description" id="description" Placeholder="Enter a description: "/>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="Save">@Localizer["Save"]</button>
<button type="button" class="btn btn-danger" @onclick="Report">@Localizer["Report"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br /><br />
@if (ReportingComponent != null)
{
<DynamicComponent Type="@ReportingComponent.ReportType" Parameters="@_parameters"/>
}
<br/><br/>
@if (PageState.Action == "Edit")
{
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
<AuditInfo CreatedBy="@_blackBoard.CreatedBy" CreatedOn="@_blackBoard.CreatedOn" ModifiedBy="@_blackBoard.ModifiedBy" ModifiedOn="@_blackBoard.ModifiedOn"></AuditInfo>
}
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string RenderMode => RenderModes.Interactive;
public override string Actions => "Add,Edit";
public override string Title => "Manage BlackBoard";
public override List<Resource> Resources => new List<Resource>()
{
new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css")
};
public override List<Resource> Resources => [new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css")];
private RichTextEditor RichTextEditorHtml;
private ElementReference form;
private bool validated = false;
private bool validated;
private int _id;
private string _name;
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
// TODO
private BlackBoard _blackBoard = new BlackBoard();
private Dictionary<string, object> _parameters = new Dictionary<string, object>();
protected override async Task OnInitializedAsync()
{
@@ -58,14 +72,10 @@
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId);
if (BlackBoard != null)
_blackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId);
if (_blackBoard != null)
{
_name = BlackBoard.Name;
_createdby = BlackBoard.CreatedBy;
_createdon = BlackBoard.CreatedOn;
_modifiedby = BlackBoard.ModifiedBy;
_modifiedon = BlackBoard.ModifiedOn;
_parameters = ReportingComponent.ConstructParameterList(_blackBoard, RenderModeBoundary);
}
}
}
@@ -76,6 +86,13 @@
}
}
private Task OnFileSelected(int fileId)
{
Console.WriteLine("File Selected: " + fileId);
_blackBoard.ImageID = fileId;
return Task.CompletedTask;
}
private async Task Save()
{
try
@@ -84,21 +101,19 @@
var interop = new Oqtane.UI.Interop(JSRuntime);
if (await interop.FormValid(form))
{
_blackBoard.Description = await RichTextEditorHtml.GetHtml();
if (PageState.Action == "Add")
{
BlackBoard BlackBoard = new BlackBoard();
BlackBoard.ModuleId = ModuleState.ModuleId;
BlackBoard.Name = _name;
BlackBoard = await BlackBoardService.AddBlackBoardAsync(BlackBoard);
await logger.LogInformation("BlackBoard Added {BlackBoard}", BlackBoard);
_blackBoard.ModuleId = ModuleState.ModuleId;
_blackBoard = await BlackBoardService.AddBlackBoardAsync(_blackBoard);
await logger.LogInformation("BlackBoard Added {BlackBoard}", _blackBoard);
}
else
{
BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId);
BlackBoard.Name = _name;
await BlackBoardService.UpdateBlackBoardAsync(BlackBoard);
await logger.LogInformation("BlackBoard Updated {BlackBoard}", BlackBoard);
await BlackBoardService.UpdateBlackBoardAsync(_blackBoard);
await logger.LogInformation("BlackBoard Updated {BlackBoard}", _blackBoard);
}
NavigationManager.NavigateTo(NavigateUrl());
}
else
@@ -112,19 +127,5 @@
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
}
}
private async Task Report()
{
try
{
BlackBoard BlackBoard = await BlackBoardService.GetBlackBoardAsync(_id, ModuleState.ModuleId);
BlackBoard.Name = _name;
ReportingHandler.Report(BlackBoard, "Reported by user");
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Reporting BlackBoard {BlackBoardId} {Error}", _id, ex.Message);
AddModuleMessage(Localizer["Message.ReportError"], MessageType.Error);
}
}
}

View File

@@ -4,76 +4,101 @@
@namespace SZUAbsolventenverein.Module.BlackBoard
@inherits ModuleBase
@inject IBlackBoardService BlackBoardService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Index> Localizer
@if (_BlackBoards == null)
@if (_blackBoards == null)
{
<p><em>Loading...</em></p>
<p>
<em>Loading...</em>
</p>
}
else
{
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add BlackBoard" ResourceKey="Add" />
<br />
<br />
@if (@_BlackBoards.Count != 0)
{
<Pager Items="@_BlackBoards">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.BlackBoardId.ToString())" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete BlackBoard" Message="Are You Sure You Wish To Delete This BlackBoard?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" ResourceKey="Delete" Id="@context.BlackBoardId.ToString()" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
else
{
<p>@Localizer["Message.DisplayNone"]</p>
}
<ActionLink Action="Add" Security="SecurityAccessLevel.Edit" Text="Add BlackBoard" ResourceKey="Add"/>
<br/>
<br/>
@if (_blackBoards.Count != 0)
{
<div class="bb-card-grid">
@foreach (var item in _blackBoards)
{
<div class="bb-card">
@if (item.ImageID > 0)
{
<img class="bb-card-img" src="@ImageUrl(item.ImageID, 600, 400)" alt="@item.Name"/>
}
else
{
<div class="bb-card-img-placeholder">
<span>📋</span>
</div>
}
<div class="bb-card-body">
<h5>@item.Name</h5>
@if (!string.IsNullOrWhiteSpace(item.Description))
{
<div class="bb-card-desc">@(item.Description)</div>
}
</div>
<div class="bb-card-meta">
<AuditInfo CreatedBy="@item.CreatedBy" CreatedOn="@item.CreatedOn" ModifiedBy="@item.ModifiedBy" ModifiedOn="@item.ModifiedOn"/>
</div>
<div class="bb-card-footer">
<ActionLink Action="Details" Parameters="@("id=" + item.BlackBoardId.ToString())" Class="btn btn-primary" Text="Details" ResourceKey="Details"/>
<ActionLink Action="Edit" Parameters="@("id=" + item.BlackBoardId.ToString())" ResourceKey="Edit"/>
<ActionDialog Header="Delete BlackBoard" Message="Are You Sure You Wish To Delete This BlackBoard?" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" ConfirmClass="absolute" OnClick="@(async () => await Delete(item))" ResourceKey="Delete" Id="@item.BlackBoardId.ToString()"/>
</div>
</div>
}
</div>
}
else
{
<p>@Localizer["Message.DisplayNone"]</p>
}
}
@code {
public override string RenderMode => RenderModes.Static;
public override string RenderMode => RenderModes.Static;
public override List<Resource> Resources => new List<Resource>()
public override List<Resource> Resources =>
[
new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css"),
new Script("_content/SZUAbsolventenverein.Module.BlackBoard/Module.js")
];
List<BlackBoard> _blackBoards;
protected override async Task OnInitializedAsync()
{
try
{
new Stylesheet("_content/SZUAbsolventenverein.Module.BlackBoard/Module.css"),
new Script("_content/SZUAbsolventenverein.Module.BlackBoard/Module.js")
};
List<BlackBoard> _BlackBoards;
protected override async Task OnInitializedAsync()
{
try
{
_BlackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading BlackBoard {Error}", ex.Message);
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
_blackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId);
}
private async Task Delete(BlackBoard BlackBoard)
catch (Exception ex)
{
try
{
await BlackBoardService.DeleteBlackBoardAsync(BlackBoard.BlackBoardId, ModuleState.ModuleId);
await logger.LogInformation("BlackBoard Deleted {BlackBoard}", BlackBoard);
_BlackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting BlackBoard {BlackBoard} {Error}", BlackBoard, ex.Message);
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
}
await logger.LogError(ex, "Error Loading BlackBoard {Error}", ex.Message);
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
private async Task Delete(BlackBoard blackBoard)
{
try
{
await BlackBoardService.DeleteBlackBoardAsync(blackBoard.BlackBoardId, ModuleState.ModuleId);
await logger.LogInformation("BlackBoard Deleted {BlackBoard}", blackBoard);
_blackBoards = await BlackBoardService.GetBlackBoardsAsync(ModuleState.ModuleId);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting BlackBoard {BlackBoard} {Error}", blackBoard, ex.Message);
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
}
}
}

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Edit.Text" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="Delete.Text" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="Delete.Header" xml:space="preserve">
<value>BlackBoard löschen</value>
</data>
<data name="Message.DeleteConfirm" xml:space="preserve">
<value>Möchten Sie diesen BlackBoard-Eintrag wirklich löschen?</value>
</data>
<data name="Message.LoadError" xml:space="preserve">
<value>Fehler beim Laden des BlackBoard-Eintrags</value>
</data>
<data name="Message.DeleteError" xml:space="preserve">
<value>Fehler beim Löschen des BlackBoard-Eintrags</value>
</data>
</root>

View File

@@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Interfaces" Version="0.0.0-12" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.1" />

View File

@@ -1,7 +1,8 @@
#!/bin/bash
TargetFramework=$1
ProjectName=$2
find . -name "*.nupkg" -delete
"..\..\oqtane.framework\oqtane.package\FixProps.exe"
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\"
find . -name *.nupkg -delete
dotnet run --project ../../fixProps/FixProps/FixProps.csproj
dotnet pack $ProjectName.nuspec "/p:targetframework=${TargetFramework};projectname=${ProjectName}"
cp -f *.nupkg ../../oqtane.framework/Oqtane.Server/Packages/

View File

@@ -1,6 +1,6 @@
<Solution>
<Project Path="..\oqtane.framework\Oqtane.Server\Oqtane.Server.csproj" DefaultStartup="true">
<Build Solution="Debug|*" Project="false" />
<Build Project="false" />
</Project>
<Project Path="Client\SZUAbsolventenverein.Module.BlackBoard.Client.csproj" />
<Project Path="Server\SZUAbsolventenverein.Module.BlackBoard.Server.csproj" />

View File

@@ -0,0 +1,38 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure;
using Oqtane.Repository;
namespace SZUAbsolventenverein.Module.BlackBoard.Infrastructure
{
public class BlackBoardDigestJob : HostedServiceBase
{
// JobType = "SZUAbsolventenverein.Module.BlackBoard.Infrastructure, SZUAbsolventenverein.Module.BlackBoard.Server.Oqtane"
public BlackBoardDigestJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "BlackBoardDigestJob";
Frequency = "m";
Interval = 1;
IsEnabled = false;
}
public override Task<string> ExecuteJobAsync(IServiceProvider provider)
{
StringBuilder log = new StringBuilder();
var sites = provider.GetRequiredService<ISiteRepository>();
foreach (var site in sites.GetSites())
{
log.AppendLine(site.Name);
}
return Task.FromResult(log.ToString());
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations;
using SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders;
using SZUAbsolventenverein.Module.BlackBoard.Repository;
namespace SZUAbsolventenverein.Module.BlackBoard.Migrations
{
[DbContext(typeof(BlackBoardContext))]
[Migration("SZUAbsolventenverein.Module.BlackBoard.01.00.00.01")]
public class AddDescriptionColumn : MultiDatabaseMigration
{
public AddDescriptionColumn(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var entityBuilder = new BlackBoardEntityBuilder(migrationBuilder, ActiveDatabase);
entityBuilder.AddStringColumn("Description", 1000, true, false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var entityBuilder = new BlackBoardEntityBuilder(migrationBuilder, ActiveDatabase);
entityBuilder.DropColumn("Description");
}
}
}

View File

@@ -12,12 +12,14 @@ namespace SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders
private const string _entityTableName = "SZUAbsolventenvereinBlackBoard";
private readonly PrimaryKey<BlackBoardEntityBuilder> _primaryKey = new("PK_SZUAbsolventenvereinBlackBoard", x => x.BlackBoardId);
private readonly ForeignKey<BlackBoardEntityBuilder> _moduleForeignKey = new("FK_SZUAbsolventenvereinBlackBoard_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
private readonly ForeignKey<BlackBoardEntityBuilder> _fileForeignKey = new("FK_SZUAbsolventenvereinBlackBoard_File", x => x.ImageID, "File", "FileId", ReferentialAction.Cascade);
public BlackBoardEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_moduleForeignKey);
ForeignKeys.Add(_fileForeignKey);
}
protected override BlackBoardEntityBuilder BuildTable(ColumnsBuilder table)
@@ -25,6 +27,7 @@ namespace SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders
BlackBoardId = AddAutoIncrementColumn(table,"BlackBoardId");
ModuleId = AddIntegerColumn(table,"ModuleId");
Name = AddMaxStringColumn(table,"Name");
ImageID = AddIntegerColumn(table,"ImageID");
AddAuditableColumns(table);
return this;
}
@@ -32,5 +35,6 @@ namespace SZUAbsolventenverein.Module.BlackBoard.Migrations.EntityBuilders
public OperationBuilder<AddColumnOperation> BlackBoardId { get; set; }
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
public OperationBuilder<AddColumnOperation> Name { get; set; }
public OperationBuilder<AddColumnOperation> ImageID { get; set; }
}
}

View File

@@ -1 +1,118 @@
/* Module Custom Styles */
/* Module Custom Styles */
/* ── Blackboard Card Grid ── */
.bb-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.bb-card {
display: flex;
flex-direction: column;
border: 1px solid #dee2e6;
border-radius: 0.75rem;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition:
box-shadow 0.2s ease,
transform 0.2s ease;
}
.bb-card:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.bb-card-img {
width: 100%;
height: 200px;
object-fit: cover;
}
.bb-card-img-placeholder {
width: 100%;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
color: #adb5bd;
font-size: 2.5rem;
}
.bb-card-body {
padding: 1.25rem;
flex: 1 1 auto;
}
.bb-card-body h5 {
margin: 0 0 0.5rem;
font-size: 1.15rem;
font-weight: 600;
color: #212529;
}
.bb-card-desc {
font-size: 0.9rem;
color: #495057;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.5;
}
.bb-card-meta {
padding: 0 1.25rem 0.75rem;
font-size: 0.8rem;
color: #6c757d;
}
.bb-card-footer {
display: flex;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
border-top: 1px solid #e9ecef;
background: #f8f9fa;
}
/* ── Blackboard Details View ── */
.bb-details .card {
background: #ffffff;
}
.bb-details .breadcrumb-item a {
text-decoration: none;
color: var(--primary);
}
.bb-detail-image-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-size: cover;
background-position: center;
filter: blur(30px) brightness(1.1);
opacity: 0.15;
z-index: 0;
}
.bb-description-section .lead {
word-wrap: break-word;
overflow-wrap: break-word;
}
.bb-description-section .lead img {
max-width: 100%;
height: auto;
border-radius: 0.5rem;
}
.bb-detail-audit {
font-size: 0.85rem;
color: #6c757d;
}

View File

@@ -12,6 +12,8 @@ namespace SZUAbsolventenverein.Module.BlackBoard.Models
public int BlackBoardId { get; set; }
public int ModuleId { get; set; }
public string Name { get; set; }
public int ImageID { get; set; }
public string Description { get; set; }
[NotMapped]
public string ModuleName => "BlackBoard";
@@ -19,5 +21,7 @@ namespace SZUAbsolventenverein.Module.BlackBoard.Models
public int ModuleID => ModuleId;
[NotMapped]
public int EntityID => BlackBoardId;
[NotMapped] public string UserName => CreatedBy;
}
}

View File

@@ -13,12 +13,10 @@
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Interfaces" Version="0.0.0-12" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interfaces">
<HintPath>..\..\interfaces\Interfaces\bin\Debug\net10.0\Interfaces.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll</HintPath></Reference>
</ItemGroup>