diff --git a/CutList.Web/Components/Shared/MaterialFilter.razor b/CutList.Web/Components/Shared/MaterialFilter.razor
new file mode 100644
index 0000000..df1980a
--- /dev/null
+++ b/CutList.Web/Components/Shared/MaterialFilter.razor
@@ -0,0 +1,79 @@
+@using CutList.Web.Data.Entities
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+ [Parameter] public IEnumerable AvailableGrades { get; set; } = Enumerable.Empty();
+ [Parameter] public MaterialFilterState Value { get; set; } = new();
+ [Parameter] public EventCallback ValueChanged { get; set; }
+
+ private async Task OnShapeChanged(ChangeEventArgs e)
+ {
+ Value.Shape = Enum.TryParse(e.Value?.ToString(), out var shape) ? shape : null;
+ await ValueChanged.InvokeAsync(Value);
+ }
+
+ private async Task OnTypeChanged(ChangeEventArgs e)
+ {
+ Value.Type = Enum.TryParse(e.Value?.ToString(), out var type) ? type : null;
+ await ValueChanged.InvokeAsync(Value);
+ }
+
+ private async Task OnGradeChanged(ChangeEventArgs e)
+ {
+ var val = e.Value?.ToString();
+ Value.Grade = string.IsNullOrEmpty(val) ? null : val;
+ await ValueChanged.InvokeAsync(Value);
+ }
+
+ private async Task OnSearchInput(ChangeEventArgs e)
+ {
+ Value.SearchText = e.Value?.ToString();
+ await ValueChanged.InvokeAsync(Value);
+ }
+
+ private async Task OnClear()
+ {
+ Value.Shape = null;
+ Value.Type = null;
+ Value.Grade = null;
+ Value.SearchText = null;
+ await ValueChanged.InvokeAsync(Value);
+ }
+}
diff --git a/CutList.Web/Components/Shared/MaterialFilterState.cs b/CutList.Web/Components/Shared/MaterialFilterState.cs
new file mode 100644
index 0000000..21381ac
--- /dev/null
+++ b/CutList.Web/Components/Shared/MaterialFilterState.cs
@@ -0,0 +1,11 @@
+using CutList.Web.Data.Entities;
+
+namespace CutList.Web.Components.Shared;
+
+public class MaterialFilterState
+{
+ public MaterialShape? Shape { get; set; }
+ public MaterialType? Type { get; set; }
+ public string? Grade { get; set; }
+ public string? SearchText { get; set; }
+}