Added network provider in Information tab
This commit is contained in:
parent
c102ab3335
commit
98abd68366
|
|
@ -753,6 +753,16 @@ public class Controller : ControllerBase
|
|||
return installation.HideParentIfUserHasNoAccessToParent(session!.User);
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetNetworkProviders))]
|
||||
public ActionResult<IReadOnlyList<string>> GetNetworkProviders(Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
if (session is null)
|
||||
return Unauthorized();
|
||||
|
||||
return Ok(NetworkProviderService.GetProviders());
|
||||
}
|
||||
|
||||
[HttpPost(nameof(AcknowledgeError))]
|
||||
public ActionResult AcknowledgeError(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,4 +54,5 @@ public class Installation : TreeNode
|
|||
public String OrderNumbers { get; set; }
|
||||
public String VrmLink { get; set; } = "";
|
||||
public string Configuration { get; set; } = "";
|
||||
public string NetworkProvider { get; set; } = "";
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ public static class Program
|
|||
LoadEnvFile();
|
||||
DiagnosticService.Initialize();
|
||||
TicketDiagnosticService.Initialize();
|
||||
NetworkProviderService.Initialize();
|
||||
AlarmReviewService.StartDailyScheduler();
|
||||
// DailyIngestionService.StartScheduler(); // Phase 2: enable when S3 auto-push is ready
|
||||
// ReportAggregationService.StartScheduler(); // Phase 2: enable when scheduler should auto-run monthly/yearly
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
using Flurl.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Fetches and caches the list of Swiss electricity network providers (Netzbetreiber)
|
||||
/// from the ELCOM/LINDAS SPARQL endpoint. Refreshes every 24 hours.
|
||||
/// </summary>
|
||||
public static class NetworkProviderService
|
||||
{
|
||||
private static IReadOnlyList<string> _providers = Array.Empty<string>();
|
||||
private static Timer? _refreshTimer;
|
||||
|
||||
private const string SparqlEndpoint = "https://ld.admin.ch/query";
|
||||
|
||||
private const string SparqlQuery = @"
|
||||
PREFIX schema: <http://schema.org/>
|
||||
SELECT DISTINCT ?name
|
||||
FROM <https://lindas.admin.ch/elcom/electricityprice>
|
||||
WHERE {
|
||||
?operator a schema:Organization ;
|
||||
schema:name ?name .
|
||||
}
|
||||
ORDER BY ?name";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// Fire-and-forget initial load
|
||||
Task.Run(RefreshAsync);
|
||||
|
||||
// Refresh every 24 hours
|
||||
_refreshTimer = new Timer(
|
||||
_ => Task.Run(RefreshAsync),
|
||||
null,
|
||||
TimeSpan.FromHours(24),
|
||||
TimeSpan.FromHours(24)
|
||||
);
|
||||
|
||||
Console.WriteLine("[NetworkProviderService] initialised.");
|
||||
}
|
||||
|
||||
public static IReadOnlyList<string> GetProviders() => _providers;
|
||||
|
||||
private static async Task RefreshAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await SparqlEndpoint
|
||||
.WithHeader("Accept", "application/sparql-results+json")
|
||||
.PostUrlEncodedAsync(new { query = SparqlQuery });
|
||||
|
||||
var json = await response.GetStringAsync();
|
||||
var parsed = JObject.Parse(json);
|
||||
|
||||
var names = parsed["results"]?["bindings"]?
|
||||
.Select(b => b["name"]?["value"]?.ToString())
|
||||
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToList();
|
||||
|
||||
if (names is { Count: > 0 })
|
||||
{
|
||||
_providers = names!;
|
||||
Console.WriteLine($"[NetworkProviderService] Loaded {names.Count} providers from ELCOM.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("[NetworkProviderService] SPARQL query returned no results.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[NetworkProviderService] Failed to fetch providers: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Alert,
|
||||
Autocomplete,
|
||||
Box,
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
|
|
@ -14,13 +15,14 @@ import {
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Close as CloseIcon } from '@mui/icons-material';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||
import { UserContext } from '../../../contexts/userContext';
|
||||
import { UserType } from '../../../interfaces/UserTypes';
|
||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||
import axiosConfig from '../../../Resources/axiosConfig';
|
||||
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
|
|
@ -53,6 +55,24 @@ function Information(props: InformationProps) {
|
|||
deleteInstallation
|
||||
} = installationContext;
|
||||
|
||||
const canEdit = currentUser.userType == UserType.admin;
|
||||
const isPartner = currentUser.userType == UserType.partner;
|
||||
const isSodistore = formValues.product === 3 || formValues.product === 4;
|
||||
|
||||
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
||||
const [loadingProviders, setLoadingProviders] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSodistore) {
|
||||
setLoadingProviders(true);
|
||||
axiosConfig
|
||||
.get('/GetNetworkProviders')
|
||||
.then((res) => setNetworkProviders(res.data))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoadingProviders(false));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormValues({
|
||||
|
|
@ -286,6 +306,54 @@ function Information(props: InformationProps) {
|
|||
error={formValues.country === ''}
|
||||
/>
|
||||
</div>
|
||||
{isSodistore && (
|
||||
<div>
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
options={networkProviders}
|
||||
value={formValues.networkProvider || ''}
|
||||
onChange={(_e, val) =>
|
||||
setFormValues({
|
||||
...formValues,
|
||||
networkProvider: (val as string) || ''
|
||||
})
|
||||
}
|
||||
onInputChange={(_e, val) =>
|
||||
setFormValues({
|
||||
...formValues,
|
||||
networkProvider: val || ''
|
||||
})
|
||||
}
|
||||
disabled={!canEdit && !isPartner}
|
||||
loading={loadingProviders}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="networkProvider"
|
||||
defaultMessage="Network Provider"
|
||||
/>
|
||||
}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{loadingProviders ? (
|
||||
<CircularProgress size={20} />
|
||||
) : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
@ -341,7 +409,7 @@ function Information(props: InformationProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{currentUser.userType == UserType.admin && (
|
||||
{canEdit && (
|
||||
<>
|
||||
<div>
|
||||
<TextField
|
||||
|
|
@ -400,7 +468,7 @@ function Information(props: InformationProps) {
|
|||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
{currentUser.userType == UserType.admin && (
|
||||
{canEdit && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleDelete}
|
||||
|
|
@ -414,7 +482,7 @@ function Information(props: InformationProps) {
|
|||
/>
|
||||
</Button>
|
||||
)}
|
||||
{currentUser.userType == UserType.admin && (
|
||||
{(canEdit || (isPartner && isSodistore)) && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Alert,
|
||||
Autocomplete,
|
||||
Box,
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
|
|
@ -26,6 +27,7 @@ import { UserContext } from '../../../contexts/userContext';
|
|||
import routes from '../../../Resources/routes.json';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { UserType } from '../../../interfaces/UserTypes';
|
||||
import axiosConfig from '../../../Resources/axiosConfig';
|
||||
|
||||
interface InformationSodistorehomeProps {
|
||||
values: I_Installation;
|
||||
|
|
@ -178,6 +180,18 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
const canEdit = currentUser.userType === UserType.admin;
|
||||
const isPartner = currentUser.userType === UserType.partner;
|
||||
|
||||
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
||||
const [loadingProviders, setLoadingProviders] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingProviders(true);
|
||||
axiosConfig
|
||||
.get('/GetNetworkProviders')
|
||||
.then((res) => setNetworkProviders(res.data))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoadingProviders(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{openModalDeleteInstallation && (
|
||||
|
|
@ -361,6 +375,52 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Autocomplete
|
||||
freeSolo
|
||||
options={networkProviders}
|
||||
value={formValues.networkProvider || ''}
|
||||
onChange={(_e, val) =>
|
||||
setFormValues({
|
||||
...formValues,
|
||||
networkProvider: (val as string) || ''
|
||||
})
|
||||
}
|
||||
onInputChange={(_e, val) =>
|
||||
setFormValues({
|
||||
...formValues,
|
||||
networkProvider: val || ''
|
||||
})
|
||||
}
|
||||
disabled={!canEdit && !isPartner}
|
||||
loading={loadingProviders}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="networkProvider"
|
||||
defaultMessage="Network Provider"
|
||||
/>
|
||||
}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{loadingProviders ? (
|
||||
<CircularProgress size={20} />
|
||||
) : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{canEdit && (
|
||||
<div>
|
||||
<TextField
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface I_Installation extends I_S3Credentials {
|
|||
testingMode?: boolean;
|
||||
status?: number;
|
||||
serialNumber?: string;
|
||||
networkProvider: string;
|
||||
}
|
||||
|
||||
export interface I_Folder {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"alarms": "Alarme",
|
||||
"applyChanges": "Änderungen speichern",
|
||||
"country": "Land",
|
||||
"networkProvider": "Netzbetreiber",
|
||||
"createNewFolder": "Neuer Ordner",
|
||||
"createNewUser": "Neuer Benutzer",
|
||||
"customerName": "Kundenname",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"allInstallations": "All installations",
|
||||
"applyChanges": "Apply changes",
|
||||
"country": "Country",
|
||||
"networkProvider": "Network Provider",
|
||||
"customerName": "Customer name",
|
||||
"english": "English",
|
||||
"german": "German",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"alarms": "Alarmes",
|
||||
"applyChanges": "Appliquer",
|
||||
"country": "Pays",
|
||||
"networkProvider": "Gestionnaire de réseau",
|
||||
"createNewFolder": "Nouveau dossier",
|
||||
"createNewUser": "Nouvel utilisateur",
|
||||
"customerName": "Nom du client",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"allInstallations": "Tutte le installazioni",
|
||||
"applyChanges": "Applica modifiche",
|
||||
"country": "Paese",
|
||||
"networkProvider": "Gestore di rete",
|
||||
"customerName": "Nome cliente",
|
||||
"english": "Inglese",
|
||||
"german": "Tedesco",
|
||||
|
|
|
|||
Loading…
Reference in New Issue