diff --git a/backend/DTOs/ReplenishmentOrder/ReplenishmentOrderDTO.cs b/backend/DTOs/ReplenishmentOrder/ReplenishmentOrderDTO.cs index 6c43d29..851d244 100644 --- a/backend/DTOs/ReplenishmentOrder/ReplenishmentOrderDTO.cs +++ b/backend/DTOs/ReplenishmentOrder/ReplenishmentOrderDTO.cs @@ -4,7 +4,7 @@ public class ReplenishmentOrderResponseDto { public Guid OrderId { get; set; } public string Isbn { get; set; } - public DateOnly OrderDate { get; set; } + public DateTime OrderDate { get; set; } public int Quantity { get; set; } public string Status { get; set; } } diff --git a/backend/Migrations/004_DateToTimestamp.sql b/backend/Migrations/004_DateToTimestamp.sql new file mode 100644 index 0000000..a48b967 --- /dev/null +++ b/backend/Migrations/004_DateToTimestamp.sql @@ -0,0 +1,19 @@ +-- Migration to change DATE columns to TIMESTAMPTZ for timezone support +-- This ensures proper handling of timezone offsets in frontend/backend +-- TIMESTAMPTZ stores timestamps in UTC internally and converts based on timezone + +-- Alter customer_order table +ALTER TABLE customer_order +ALTER COLUMN order_date TYPE TIMESTAMPTZ +USING order_date::TIMESTAMPTZ; + +ALTER TABLE customer_order +ALTER COLUMN order_date SET DEFAULT CURRENT_TIMESTAMP; + +-- Alter replenishment_order table +ALTER TABLE replenishment_order +ALTER COLUMN order_date TYPE TIMESTAMPTZ +USING order_date::TIMESTAMPTZ; + +ALTER TABLE replenishment_order +ALTER COLUMN order_date SET DEFAULT CURRENT_TIMESTAMP; diff --git a/backend/Models/ReplenishmentOrder.cs b/backend/Models/ReplenishmentOrder.cs index 91831be..f79d6b5 100644 --- a/backend/Models/ReplenishmentOrder.cs +++ b/backend/Models/ReplenishmentOrder.cs @@ -4,7 +4,7 @@ public class ReplenishmentOrder { public Guid OrderId { get; set; } public string Isbn { get; set; } - public DateOnly OrderDate { get; set; } + public DateTime OrderDate { get; set; } public int Quantity { get; set; } public string Status { get; set; } } diff --git a/backend/Program.cs b/backend/Program.cs index 074d3f5..bb3b858 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -17,6 +17,9 @@ var builder = WebApplication.CreateBuilder(args); Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true; +// Configure Npgsql to read timestamps as UTC +AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", false); + // Load DB Connection var connectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING"); if (string.IsNullOrEmpty(connectionString)) @@ -41,6 +44,8 @@ .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; + // Ensure DateTime values are serialized as UTC with 'Z' suffix + options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); }); diff --git a/backend/Repositories/CustomerOrder/CustomerOrderRepository.cs b/backend/Repositories/CustomerOrder/CustomerOrderRepository.cs index b5980ca..d593db8 100644 --- a/backend/Repositories/CustomerOrder/CustomerOrderRepository.cs +++ b/backend/Repositories/CustomerOrder/CustomerOrderRepository.cs @@ -28,12 +28,18 @@ INSERT INTO customer_order_item(order_id, isbn, quantity, price) public async Task> GetOrdersAsync(Guid userId) { const string sql = @" - SELECT order_id as OrderId, u_id as UserId, order_date::timestamp as OrderDate, total_price as TotalPrice + SELECT order_id as OrderId, u_id as UserId, order_date AT TIME ZONE 'UTC' as OrderDate, total_price as TotalPrice FROM customer_order WHERE u_id = @UserId ORDER BY order_date DESC; "; - return (await _db.QueryAsync(sql, new { UserId = userId })).ToList(); + var orders = await _db.QueryAsync(sql, new { UserId = userId }); + // Ensure DateTime is marked as UTC + foreach (var order in orders) + { + order.OrderDate = DateTime.SpecifyKind(order.OrderDate, DateTimeKind.Utc); + } + return orders.ToList(); } public async Task> GetOrderItemsAsync(Guid orderId) diff --git a/backend/Repositories/ReplenishmentOrder/ReplenishmentOrderRepository.cs b/backend/Repositories/ReplenishmentOrder/ReplenishmentOrderRepository.cs index f3e2165..71e2a89 100644 --- a/backend/Repositories/ReplenishmentOrder/ReplenishmentOrderRepository.cs +++ b/backend/Repositories/ReplenishmentOrder/ReplenishmentOrderRepository.cs @@ -15,17 +15,28 @@ public ReplenishmentOrderRepository(IDbConnection db) public async Task> GetAllAsync() { - return await _db.QueryAsync( + var orders = await _db.QueryAsync( "SELECT * FROM replenishment_order ORDER BY order_date DESC;" ); + // Ensure DateTime is marked as UTC + foreach (var order in orders) + { + order.OrderDate = DateTime.SpecifyKind(order.OrderDate, DateTimeKind.Utc); + } + return orders; } public async Task GetByIdAsync(Guid orderId) { - return await _db.QuerySingleOrDefaultAsync( + var order = await _db.QuerySingleOrDefaultAsync( "SELECT * FROM replenishment_order WHERE order_id = @OrderId;", new { OrderId = orderId } ); + if (order != null) + { + order.OrderDate = DateTime.SpecifyKind(order.OrderDate, DateTimeKind.Utc); + } + return order; } public async Task ConfirmAsync(Guid orderId) diff --git a/frontend/app/admin/home/page.tsx b/frontend/app/admin/home/page.tsx index 55a2a50..5c8e827 100644 --- a/frontend/app/admin/home/page.tsx +++ b/frontend/app/admin/home/page.tsx @@ -125,10 +125,14 @@ export default function AdminDashboard() { const formatDate = (dateString: string) => { const date = new Date(dateString); - return date.toLocaleDateString("en-US", { + return date.toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", + hour: "2-digit", + minute: "2-digit", + timeZoneName: "short", + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); }; diff --git a/frontend/app/admin/publisher-orders/page.tsx b/frontend/app/admin/publisher-orders/page.tsx index 66cf672..c9a58c6 100644 --- a/frontend/app/admin/publisher-orders/page.tsx +++ b/frontend/app/admin/publisher-orders/page.tsx @@ -91,10 +91,14 @@ export default function PublisherOrdersPage() { const formatDate = (dateString: string) => { const date = new Date(dateString); - return date.toLocaleDateString("en-US", { + return date.toLocaleString(undefined, { year: "numeric", month: "long", day: "numeric", + hour: "2-digit", + minute: "2-digit", + timeZoneName: "short", + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); }; diff --git a/frontend/app/user/orders/page.tsx b/frontend/app/user/orders/page.tsx index fa1385e..bce9882 100644 --- a/frontend/app/user/orders/page.tsx +++ b/frontend/app/user/orders/page.tsx @@ -64,21 +64,25 @@ export default function OrdersPage() { const formatDate = (dateString: string) => { const date = new Date(dateString); - return date.toLocaleDateString("en-US", { + return date.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); }; const formatDateTime = (dateString: string) => { const date = new Date(dateString); - return date.toLocaleString("en-US", { + return date.toLocaleString(undefined, { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", + second: "2-digit", + timeZoneName: "short", + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, }); };