diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DateTimeRangeFilter.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DateTimeRangeFilter.razor
new file mode 100644
index 00000000..9c940f9d
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/DateTimeRangeFilter.razor
@@ -0,0 +1,57 @@
+@* Reusable from/to datetime-local input pair for filter bars.
+ INPUT-ONLY: emits raw DateTime? (Unspecified kind) via FromChanged / ToChanged.
+ UTC conversion is the caller's responsibility — no Apply button, no service injection. *@
+
+
+
+@code {
+ [Parameter] public DateTime? From { get; set; }
+ [Parameter] public EventCallback FromChanged { get; set; }
+ [Parameter] public DateTime? To { get; set; }
+ [Parameter] public EventCallback ToChanged { get; set; }
+
+ /// Prefix applied to all id/data-test attributes. Pages pass short codes like "no", "sc", "audit-filter".
+ [Parameter] public string IdPrefix { get; set; } = "dtr";
+
+ [Parameter] public string FromLabel { get; set; } = "From";
+ [Parameter] public string ToLabel { get; set; } = "To";
+
+ private async Task OnFromChanged(ChangeEventArgs e)
+ {
+ var parsed = ParseInput(e.Value?.ToString());
+ await FromChanged.InvokeAsync(parsed);
+ }
+
+ private async Task OnToChanged(ChangeEventArgs e)
+ {
+ var parsed = ParseInput(e.Value?.ToString());
+ await ToChanged.InvokeAsync(parsed);
+ }
+
+ private static DateTime? ParseInput(string? raw)
+ {
+ if (string.IsNullOrWhiteSpace(raw))
+ return null;
+
+ return DateTime.TryParse(raw, out var dt) ? dt : null;
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/DateTimeRangeFilterTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/DateTimeRangeFilterTests.cs
new file mode 100644
index 00000000..670cae99
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/DateTimeRangeFilterTests.cs
@@ -0,0 +1,87 @@
+using Bunit;
+using Microsoft.AspNetCore.Components;
+using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;
+
+///
+/// Component tests for — the reusable from/to
+/// datetime-local input pair. The component is INPUT-ONLY: it emits raw
+/// DateTime? (Unspecified kind); UTC conversion is the page's responsibility.
+///
+public class DateTimeRangeFilterTests : BunitContext
+{
+ [Fact]
+ public void Renders_TwoInputs_WithPrefixedIdsAndTestHooks()
+ {
+ var cut = Render(ps => ps
+ .Add(p => p.IdPrefix, "sc"));
+
+ var fromInput = cut.Find("[data-test='sc-from']");
+ var toInput = cut.Find("[data-test='sc-to']");
+
+ Assert.Equal("datetime-local", fromInput.GetAttribute("type"));
+ Assert.Equal("datetime-local", toInput.GetAttribute("type"));
+ Assert.Equal("sc-from", fromInput.GetAttribute("id"));
+ Assert.Equal("sc-to", toInput.GetAttribute("id"));
+ }
+
+ [Fact]
+ public void RendersLabels()
+ {
+ var cut = Render(ps => ps
+ .Add(p => p.FromLabel, "Start")
+ .Add(p => p.ToLabel, "End"));
+
+ cut.Find("label[for='dtr-from']"); // default IdPrefix = "dtr"
+ cut.Find("label[for='dtr-to']");
+
+ Assert.Contains("Start", cut.Markup);
+ Assert.Contains("End", cut.Markup);
+ }
+
+ [Fact]
+ public void SettingFromInput_InvokesFromChanged_WithParsedValue()
+ {
+ DateTime? captured = default;
+ var cut = Render(ps => ps
+ .Add(p => p.IdPrefix, "sc")
+ .Add(p => p.FromChanged,
+ EventCallback.Factory.Create(this, v => captured = v)));
+
+ cut.Find("[data-test='sc-from']").Change("2026-06-01T08:30");
+
+ Assert.NotNull(captured);
+ Assert.Equal(2026, captured!.Value.Year);
+ Assert.Equal(6, captured.Value.Month);
+ Assert.Equal(1, captured.Value.Day);
+ Assert.Equal(8, captured.Value.Hour);
+ Assert.Equal(30, captured.Value.Minute);
+ }
+
+ [Fact]
+ public void ClearingFromInput_InvokesFromChanged_WithNull()
+ {
+ DateTime? captured = new DateTime(2026, 1, 1);
+ var cut = Render(ps => ps
+ .Add(p => p.IdPrefix, "sc")
+ .Add(p => p.From, new DateTime(2026, 1, 1))
+ .Add(p => p.FromChanged,
+ EventCallback.Factory.Create(this, v => captured = v)));
+
+ cut.Find("[data-test='sc-from']").Change("");
+
+ Assert.Null(captured);
+ }
+
+ [Fact]
+ public void InitialValue_RendersInInput()
+ {
+ var initial = new DateTime(2025, 12, 31, 23, 59, 0);
+ var cut = Render(ps => ps
+ .Add(p => p.From, initial));
+
+ var value = cut.Find("[data-test='dtr-from']").GetAttribute("value");
+ Assert.Equal("2025-12-31T23:59", value);
+ }
+}