Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CulinaryCommandApp/Components/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
<span class="bi bi-people nav-icon"></span> Manage Users
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="margin-edge">
<span class="bi bi-terminal nav-icon"></span> MarginEdge
</NavLink>
</div>
}
else if (_role == "Manager")
{
Expand Down
250 changes: 250 additions & 0 deletions CulinaryCommandApp/Components/Pages/MarginEdge.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
@page "/margin-edge"
@using CulinaryCommand.Services
@using CulinaryCommand.Services.UserContextSpace
@using System.Web @* For HttpUtility if needed, but simple string building works too *@
@using System.Text.Json

@inject IUserContextService UserCtx
@inject HttpClient Http

<div class="container mt-4">
<h3>MarginEdge API Tester</h3>
<hr />

<div class="row mb-3">
<div class="col-md-6">
<label class="form-label fw-bold">API Key</label>
<input type="password" class="form-control" @bind="apiKey" placeholder="Enter MarginEdge API Key" />
</div>
</div>

<div class="row g-3 mb-3">
<div class="col-md-4">
<label class="form-label fw-bold">Endpoint</label>
<select class="form-select" @onchange="OnEndpointChanged">
@foreach (var endpoint in Endpoints)
{
<option value="@endpoint.Path">@endpoint.Name (@endpoint.Method)</option>
}
</select>
</div>

@if (requiresLocationId) {
<div class="col-md-3">
<label class="form-label fw-bold">Location ID (restaurantUnitId)</label>
<input type="text" class="form-control" @bind="locationId" placeholder="GUID..." />
</div>
}

@if (requiresId)
{
<div class="col-md-3">
<label class="form-label fw-bold">Resource ID (order/vendor)</label>
<input type="text" class="form-control" @bind="resourceId" placeholder="ID..." />
</div>
}
</div>

@if (currentPath == "/orders")
{
<div class="row g-3 mb-3">
<div class="col-md-3">
<label class="form-label fw-bold">Start Date</label>
<input type="date" class="form-control" @bind="startDate" />
</div>
<div class="col-md-3">
<label class="form-label fw-bold">End Date</label>
<input type="date" class="form-control" @bind="endDate" />
</div>
</div>
}

<div class="row mb-3">
<div class="col-md-2">
<button class="btn btn-primary w-100" @onclick="ExecuteQuery" disabled="@string.IsNullOrEmpty(apiKey)">Run Query</button>
</div>
</div>

@* @if (!string.IsNullOrEmpty(apiResponse))
{
<div class="mt-4">
<h5>Response Result:</h5>
<pre class="p-3 bg-dark text-light border rounded" style="max-height: 600px; overflow: auto;">@apiResponse</pre>
</div>
} *@
@if (!string.IsNullOrEmpty(apiResponse))
{
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5>Results:</h5>
<div>
<button class="btn btn-sm @(viewMode == "json" ? "btn-primary" : "btn-outline-primary")" @onclick='() => viewMode = "json"'>JSON</button>
<button class="btn btn-sm @(viewMode == "cards" ? "btn-primary" : "btn-outline-primary")" @onclick='() => viewMode = "cards"'>Cards</button>
</div>
</div>

@if (viewMode == "json")
{
<pre class="p-3 bg-dark text-light border rounded" style="max-height: 500px; overflow: auto;">@apiResponse</pre>
}
else
{
<div class="row g-3" style="max-width: 100%; overflow-x: hidden;">
@try
{
var doc = JsonDocument.Parse(apiResponse);
// MarginEdge often returns an array at the root or inside a "content" property

IEnumerable<JsonElement> items;
if (doc.RootElement.ValueKind == JsonValueKind.Array)
{
items = doc.RootElement.EnumerateArray();
}
else
{
// Find the first array property at the root — handles "restaurants", "content", "orders", etc.
var arrayProp = doc.RootElement.EnumerateObject()
.FirstOrDefault(p => p.Value.ValueKind == JsonValueKind.Array);
items = arrayProp.Value.ValueKind == JsonValueKind.Array
? arrayProp.Value.EnumerateArray()
: Enumerable.Empty<JsonElement>();
}

@foreach (var item in items)
{
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
@* <h6 class="card-subtitle mb-2 text-muted">@GetSafeString(item, "id")</h6>
<h5 class="card-title">@GetSafeString(item, "name", "orderNumber")</h5>
<hr /> *@
<div class="small">
<div class="small">
@foreach (var prop in item.EnumerateObject().Take(8))
{
var valStr = prop.Value.ValueKind == JsonValueKind.Object || prop.Value.ValueKind == JsonValueKind.Array
? $"[{prop.Value.ValueKind}]"
: prop.Value.ToString();
<div class="d-flex justify-content-between mb-1">
<span class="text-secondary text-nowrap">@prop.Name:</span>
<span class="fw-bold text-truncate ms-2" title="@valStr">@valStr</span>
</div>
}
</div>
</div>
</div>
</div>
</div>
}
}
catch
{
<div class="alert alert-warning">Could not parse response into cards. It might not be a list.</div>
}
</div>
}
</div>
}
</div>

@code {
private string apiKey = "";
private string apiResponse = "";
private string resourceId = "";
private string locationId = "";
private string currentPath = "/restaurantUnits";

private DateTime startDate = DateTime.Today.AddDays(-7);
private DateTime endDate = DateTime.Today;

private bool requiresLocationId =>
new[] { "orders", "products", "categories", "vendors" }
.Any(keyword => currentPath.Contains(keyword, StringComparison.OrdinalIgnoreCase));

private bool requiresId => currentPath.Contains(":");

private List<EndpointInfo> Endpoints = new()
{
new("Get available restaurants", "/restaurantUnits", "GET"),
new("Get group categories", "/restaurantUnits/groupCategories", "GET"),
new("Get unit groups", "/restaurantUnits/groups", "GET"),
new("Get orders", "/orders", "GET"),
new("Get order detail", "/orders/:orderId", "GET"),
new("Get products", "/products", "GET"),
new("Get categories", "/categories", "GET"),
new("Get vendors", "/vendors", "GET"),
new("Get vendor items", "/vendors/:vendorId/vendorItems", "GET")
};

private void OnEndpointChanged(ChangeEventArgs e)
{
currentPath = e.Value?.ToString() ?? "";
apiResponse = "";
}

private async Task ExecuteQuery()
{
try
{
apiResponse = "Loading...";

// Build URL
var path = currentPath;

// Swap :orderId / :vendorId placeholders
if (requiresId && !string.IsNullOrEmpty(resourceId))
path = System.Text.RegularExpressions.Regex.Replace(path, @":[^/]+", resourceId);

var baseUrl = $"https://api.marginedge.com/public{path}";
var query = new System.Collections.Generic.List<string>();

if (requiresLocationId && !string.IsNullOrEmpty(locationId))
query.Add($"restaurantUnitId={locationId}");

if (currentPath == "/orders")
{
query.Add($"startDate={startDate:yyyy-MM-dd}");
query.Add($"endDate={endDate:yyyy-MM-dd}");
}

var finalUrl = query.Count > 0 ? $"{baseUrl}?{string.Join("&", query)}" : baseUrl;

var request = new HttpRequestMessage(HttpMethod.Get, finalUrl);
request.Headers.Add("X-API-KEY", apiKey);

var response = await Http.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();

try
{
var jsonElement = JsonSerializer.Deserialize<JsonElement>(content);
apiResponse = JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions
{
WriteIndented = true
});
}
catch
{
apiResponse = content;
}
}
catch (Exception ex)
{
apiResponse = $"Error: {ex.Message}";
}
}

public record EndpointInfo(string Name, string Path, string Method);

private string viewMode = "json"; // Default view

// Helper to try multiple property names (like 'name' or 'orderNumber')
private string GetSafeString(System.Text.Json.JsonElement el, params string[] propertyNames)
{
foreach (var name in propertyNames)
{
if (el.TryGetProperty(name, out System.Text.Json.JsonElement prop)) return prop.ToString();
}
return "N/A";
}
}
Loading