Generalised datetime picker into component for reuse
continuous-integration/drone/pr Build is passing
continuous-integration/drone/pr Build is passing
This commit is contained in:
@@ -5,6 +5,11 @@ namespace ShopAdmin.Components;
|
||||
|
||||
public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Service s3Service)
|
||||
{
|
||||
private bool isCalendarOpen = false;
|
||||
private DateTime calendarViewingMonth = DateTime.Today;
|
||||
private List<DateTime?> calendarDays = new();
|
||||
private string currentCalendarMonthYearText => calendarViewingMonth.ToString("MMMM yyyy");
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new();
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@@ -36,13 +41,10 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
try
|
||||
{
|
||||
var file = e.File;
|
||||
|
||||
if (file == null) return;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
await file.OpenReadStream(MaxAllowedFileSize).CopyToAsync(stream, cancellationToken);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var result = await s3Service.UploadFileAsync(file.Name, stream,
|
||||
@@ -51,7 +53,6 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
ProductModel.ImageUrl = result.Value;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -64,16 +65,13 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
public void SetPreviewActive(string? url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url)) return;
|
||||
|
||||
ActivePreviewUrl = url;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void ClosePreviewDrawer()
|
||||
{
|
||||
ActivePreviewUrl = null;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -82,13 +80,10 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
try
|
||||
{
|
||||
var file = e.File;
|
||||
|
||||
if (file == null) return;
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
await file.OpenReadStream(MaxAllowedFileSize, cancellationToken).CopyToAsync(stream, cancellationToken);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var result = await s3Service.UploadFileAsync(file.Name, stream,
|
||||
@@ -97,7 +92,6 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
if (result.IsSuccess && index < ProductModel.Thumbnails.Count)
|
||||
{
|
||||
ProductModel.Thumbnails[index] = result.Value;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -112,11 +106,9 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
if (string.IsNullOrEmpty(ProductModel.ImageUrl)) return;
|
||||
|
||||
var targetUrl = ProductModel.ImageUrl;
|
||||
|
||||
if (ActivePreviewUrl == targetUrl) ActivePreviewUrl = null;
|
||||
|
||||
ProductModel.ImageUrl = null;
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
var result = await s3Service.DeleteFileAsync(GetFileKeyFromUrl(targetUrl));
|
||||
@@ -130,13 +122,11 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
if (index < 0 || index >= ProductModel.Thumbnails.Count) return;
|
||||
|
||||
var targetUrl = ProductModel.Thumbnails[index];
|
||||
|
||||
if (string.IsNullOrEmpty(targetUrl)) return;
|
||||
|
||||
if (ActivePreviewUrl == targetUrl) ActivePreviewUrl = null;
|
||||
|
||||
ProductModel.Thumbnails[index] = string.Empty;
|
||||
|
||||
StateHasChanged();
|
||||
|
||||
var result = await s3Service.DeleteFileAsync(GetFileKeyFromUrl(targetUrl));
|
||||
@@ -144,6 +134,57 @@ public partial class CreateProduct([FromKeyedServices(BookshopBucketName)] IS3Se
|
||||
if (result.IsFailed)
|
||||
Console.WriteLine($"[S3 Thumbnail Cleanup Failure]: {result.Errors[0].Message}");
|
||||
}
|
||||
|
||||
private void ToggleCalendar()
|
||||
{
|
||||
if (!isCalendarOpen)
|
||||
{
|
||||
// Default viewport context to currently selected value or fallback to today
|
||||
calendarViewingMonth = ProductModel.PublishDate;
|
||||
RebuildCalendarMatrix();
|
||||
}
|
||||
isCalendarOpen = !isCalendarOpen;
|
||||
}
|
||||
|
||||
private void RebuildCalendarMatrix()
|
||||
{
|
||||
calendarDays.Clear();
|
||||
|
||||
var firstDayOfMonth = new DateTime(calendarViewingMonth.Year, calendarViewingMonth.Month, 1);
|
||||
var totalDaysInMonth = DateTime.DaysInMonth(calendarViewingMonth.Year, calendarViewingMonth.Month);
|
||||
|
||||
// Offset leading days to align day positions correctly with day of week headers
|
||||
int leadingOffsets = (int)firstDayOfMonth.DayOfWeek;
|
||||
for (int i = 0; i < leadingOffsets; i++)
|
||||
{
|
||||
calendarDays.Add(null);
|
||||
}
|
||||
|
||||
// Populate active dates
|
||||
for (int day = 1; day <= totalDaysInMonth; day++)
|
||||
{
|
||||
calendarDays.Add(new DateTime(calendarViewingMonth.Year, calendarViewingMonth.Month, day));
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateToPreviousMonth()
|
||||
{
|
||||
calendarViewingMonth = calendarViewingMonth.AddMonths(-1);
|
||||
RebuildCalendarMatrix();
|
||||
}
|
||||
|
||||
private void NavigateToNextMonth()
|
||||
{
|
||||
calendarViewingMonth = calendarViewingMonth.AddMonths(1);
|
||||
RebuildCalendarMatrix();
|
||||
}
|
||||
|
||||
private void SelectCalendarDate(DateTime date)
|
||||
{
|
||||
ProductModel.PublishDate = date;
|
||||
isCalendarOpen = false; // Collapse popup smoothly on successful selection
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateProductModel
|
||||
@@ -160,6 +201,19 @@ public class CreateProductModel
|
||||
[Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than zero.")]
|
||||
public decimal Price { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Author metadata is required.")]
|
||||
public string? Author { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Publication Date is required.")]
|
||||
public DateTime PublishDate { get; set; } = DateTime.Today;
|
||||
|
||||
[Required(ErrorMessage = "Copyright Information field is required.")]
|
||||
public string? CopyrightInfo { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "ISBN code is required.")]
|
||||
[RegularExpression(@"^(?=(?:\D*\d){10}(?:(?:\D*\d){3})?$)[\d-]+$", ErrorMessage = "Please enter a valid ISBN-10 or ISBN-13 string.")]
|
||||
public string? Isbn { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Primary image is required.")]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user