allow more flexible email frequency
This commit is contained in:
parent
e989db54c2
commit
b0bcf06d4e
|
|
@ -922,6 +922,43 @@ public class Controller : ControllerBase
|
|||
});
|
||||
}
|
||||
|
||||
// ── Email Preferences ──────────────────────────────────────────────
|
||||
|
||||
[HttpGet(nameof(GetEmailPreference))]
|
||||
public ActionResult GetEmailPreference(Int64 installationId, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var pref = Db.GetEmailPreference(installationId);
|
||||
return Ok(new
|
||||
{
|
||||
installationId,
|
||||
sendWeekly = pref?.SendWeekly ?? false,
|
||||
sendMonthly = pref?.SendMonthly ?? false,
|
||||
sendYearly = pref?.SendYearly ?? false
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost(nameof(UpdateEmailPreference))]
|
||||
public ActionResult UpdateEmailPreference(
|
||||
Int64 installationId, Boolean sendWeekly, Boolean sendMonthly,
|
||||
Boolean sendYearly, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
Db.UpsertEmailPreference(new EmailPreference
|
||||
{
|
||||
InstallationId = installationId,
|
||||
SendWeekly = sendWeekly,
|
||||
SendMonthly = sendMonthly,
|
||||
SendYearly = sendYearly
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// ── Weekly Performance Report ──────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class EmailPreference
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Int64 InstallationId { get; set; }
|
||||
public Boolean SendWeekly { get; set; }
|
||||
public Boolean SendMonthly { get; set; }
|
||||
public Boolean SendYearly { get; set; }
|
||||
}
|
||||
|
|
@ -67,4 +67,5 @@ public class Installation : TreeNode
|
|||
public String VrmLink { get; set; } = "";
|
||||
public string Configuration { get; set; } = "";
|
||||
public string NetworkProvider { get; set; } = "";
|
||||
public string Email { get; set; } = "";
|
||||
}
|
||||
|
|
@ -75,6 +75,13 @@ public static partial class Db
|
|||
public static Boolean Create(HourlyEnergyRecord record) => Insert(record);
|
||||
public static Boolean Create(AiInsightCache cache) => Insert(cache);
|
||||
|
||||
public static Boolean UpsertEmailPreference(EmailPreference pref)
|
||||
{
|
||||
var success = Connection.InsertOrReplace(pref) > 0;
|
||||
if (success) Backup();
|
||||
return success;
|
||||
}
|
||||
|
||||
// Ticket system
|
||||
public static Boolean Create(Ticket ticket) => Insert(ticket);
|
||||
public static Boolean Create(TicketComment comment) => Insert(comment);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public static partial class Db
|
|||
public static TableQuery<DailyEnergyRecord> DailyRecords => Connection.Table<DailyEnergyRecord>();
|
||||
public static TableQuery<HourlyEnergyRecord> HourlyRecords => Connection.Table<HourlyEnergyRecord>();
|
||||
public static TableQuery<AiInsightCache> AiInsightCaches => Connection.Table<AiInsightCache>();
|
||||
public static TableQuery<EmailPreference> EmailPreferences => Connection.Table<EmailPreference>();
|
||||
|
||||
// Ticket system tables
|
||||
public static TableQuery<Ticket> Tickets => Connection.Table<Ticket>();
|
||||
|
|
@ -69,6 +70,7 @@ public static partial class Db
|
|||
Connection.CreateTable<DailyEnergyRecord>();
|
||||
Connection.CreateTable<HourlyEnergyRecord>();
|
||||
Connection.CreateTable<AiInsightCache>();
|
||||
Connection.CreateTable<EmailPreference>();
|
||||
|
||||
// Ticket system tables
|
||||
Connection.CreateTable<Ticket>();
|
||||
|
|
@ -125,6 +127,7 @@ public static partial class Db
|
|||
fileConnection.CreateTable<DailyEnergyRecord>();
|
||||
fileConnection.CreateTable<HourlyEnergyRecord>();
|
||||
fileConnection.CreateTable<AiInsightCache>();
|
||||
fileConnection.CreateTable<EmailPreference>();
|
||||
|
||||
// Ticket system tables
|
||||
fileConnection.CreateTable<Ticket>();
|
||||
|
|
|
|||
|
|
@ -161,6 +161,11 @@ public static partial class Db
|
|||
&& c.Language == language)
|
||||
?.InsightText;
|
||||
|
||||
// ── EmailPreference Queries ─────────────────────────────────────────
|
||||
|
||||
public static EmailPreference? GetEmailPreference(Int64 installationId)
|
||||
=> EmailPreferences.FirstOrDefault(p => p.InstallationId == installationId);
|
||||
|
||||
// ── Ticket Queries ──────────────────────────────────────────────────
|
||||
|
||||
public static Ticket? GetTicketById(Int64 id)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ public static class ReportAggregationService
|
|||
generated++;
|
||||
|
||||
Console.WriteLine($"[ReportAggregation] Weekly report generated for installation {installation.Id} ({installation.Name})");
|
||||
|
||||
// Auto-send email if preference is set
|
||||
await TryAutoSendWeeklyEmail(installation, report);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -416,6 +419,9 @@ public static class ReportAggregationService
|
|||
});
|
||||
|
||||
Console.WriteLine($"[ReportAggregation] Monthly report created for installation {installationId}, {year}-{month:D2} ({days.Count} days, {first}–{last}).");
|
||||
|
||||
// Auto-send email if preference is set
|
||||
await TryAutoSendMonthlyEmail(installationId, monthlySummary);
|
||||
}
|
||||
|
||||
// ── Year-End Aggregation ──────────────────────────────────────────
|
||||
|
|
@ -529,6 +535,82 @@ public static class ReportAggregationService
|
|||
});
|
||||
|
||||
Console.WriteLine($"[ReportAggregation] Yearly report created for installation {installationId}, {year} ({monthlies.Count} months aggregated).");
|
||||
|
||||
// Auto-send email if preference is set
|
||||
await TryAutoSendYearlyEmail(installationId, yearlySummary);
|
||||
}
|
||||
|
||||
// ── Auto-Send Email Helpers ─────────────────────────────────────────
|
||||
|
||||
private static async Task TryAutoSendWeeklyEmail(Installation installation, WeeklyReportResponse report)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pref = Db.GetEmailPreference(installation.Id);
|
||||
if (pref is not { SendWeekly: true }) return;
|
||||
|
||||
var email = installation.Email;
|
||||
if (String.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
Console.WriteLine($"[AutoSend] Weekly: skipping installation {installation.Id} — no email configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
await ReportEmailService.SendReportEmailAsync(report, email, "en", installation.Name);
|
||||
Console.WriteLine($"[AutoSend] Weekly email sent to {email} for installation {installation.Id}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[AutoSend] Weekly email failed for installation {installation.Id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task TryAutoSendMonthlyEmail(Int64 installationId, MonthlyReportSummary report)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pref = Db.GetEmailPreference(installationId);
|
||||
if (pref is not { SendMonthly: true }) return;
|
||||
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
var email = installation?.Email;
|
||||
if (String.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
Console.WriteLine($"[AutoSend] Monthly: skipping installation {installationId} — no email configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
await ReportEmailService.SendMonthlyReportEmailAsync(report, installation!.Name, email, "en");
|
||||
Console.WriteLine($"[AutoSend] Monthly email sent to {email} for installation {installationId}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[AutoSend] Monthly email failed for installation {installationId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task TryAutoSendYearlyEmail(Int64 installationId, YearlyReportSummary report)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pref = Db.GetEmailPreference(installationId);
|
||||
if (pref is not { SendYearly: true }) return;
|
||||
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
var email = installation?.Email;
|
||||
if (String.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
Console.WriteLine($"[AutoSend] Yearly: skipping installation {installationId} — no email configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
await ReportEmailService.SendYearlyReportEmailAsync(report, installation!.Name, email, "en");
|
||||
Console.WriteLine($"[AutoSend] Yearly email sent to {email} for installation {installationId}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[AutoSend] Yearly email failed for installation {installationId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── AI Insight Cache ──────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -708,6 +708,18 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={<FormattedMessage id="emailAddress" defaultMessage="Email Address" />}
|
||||
name="email"
|
||||
value={formValues.email || ''}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputProps={{ readOnly: !canEdit }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormControl fullWidth sx={{ marginLeft: 1, marginTop: 1, marginBottom: 1, width: 440 }}>
|
||||
<InputLabel sx={{ fontSize: 14, backgroundColor: 'white' }}>
|
||||
|
|
|
|||
|
|
@ -619,6 +619,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
|||
<WeeklyReport
|
||||
installationId={props.current_installation.id}
|
||||
installationName={props.current_installation.name}
|
||||
installationEmail={props.current_installation.email}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -7,20 +7,24 @@ import {
|
|||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
Container,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
Snackbar,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import axiosConfig from 'src/Resources/axiosConfig';
|
||||
import DailySection from './DailySection';
|
||||
|
|
@ -28,6 +32,7 @@ import DailySection from './DailySection';
|
|||
interface WeeklyReportProps {
|
||||
installationId: number;
|
||||
installationName?: string;
|
||||
installationEmail?: string;
|
||||
}
|
||||
|
||||
interface DailyEnergyData {
|
||||
|
|
@ -224,7 +229,7 @@ function EmailBar({ onSend, disabled }: { onSend: (email: string) => Promise<voi
|
|||
|
||||
// ── Main Component ─────────────────────────────────────────────
|
||||
|
||||
function WeeklyReport({ installationId, installationName }: WeeklyReportProps) {
|
||||
function WeeklyReport({ installationId, installationName, installationEmail }: WeeklyReportProps) {
|
||||
const intl = useIntl();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [monthlyReports, setMonthlyReports] = useState<MonthlyReport[]>([]);
|
||||
|
|
@ -234,13 +239,49 @@ function WeeklyReport({ installationId, installationName }: WeeklyReportProps) {
|
|||
const [generating, setGenerating] = useState<string | null>(null);
|
||||
const [selectedMonthlyIdx, setSelectedMonthlyIdx] = useState(0);
|
||||
const [selectedYearlyIdx, setSelectedYearlyIdx] = useState(0);
|
||||
const [regenerating, setRegenerating] = useState(false);
|
||||
const [dailyHasData, setDailyHasData] = useState(false);
|
||||
const [weeklyHasData, setWeeklyHasData] = useState(false);
|
||||
const [downloadingPdf, setDownloadingPdf] = useState(false);
|
||||
const [reportPeriod, setReportPeriod] = useState<{ start: string; end: string; year?: number; month?: number } | null>(null);
|
||||
const weeklyRef = useRef<WeeklySectionHandle>(null);
|
||||
|
||||
// Auto-send email preferences
|
||||
const [autoSend, setAutoSend] = useState({ sendWeekly: false, sendMonthly: false, sendYearly: false });
|
||||
const [autoSendDirty, setAutoSendDirty] = useState(false);
|
||||
const [savingAutoSend, setSavingAutoSend] = useState(false);
|
||||
const [autoSendSnackbar, setAutoSendSnackbar] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
axiosConfig.get('/GetEmailPreference', { params: { installationId } })
|
||||
.then(res => {
|
||||
setAutoSend({ sendWeekly: res.data.sendWeekly, sendMonthly: res.data.sendMonthly, sendYearly: res.data.sendYearly });
|
||||
setAutoSendDirty(false);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [installationId]);
|
||||
|
||||
const handleAutoSendChange = (field: 'sendWeekly' | 'sendMonthly' | 'sendYearly') => {
|
||||
setAutoSend(prev => ({ ...prev, [field]: !prev[field] }));
|
||||
setAutoSendDirty(true);
|
||||
};
|
||||
|
||||
const handleSaveAutoSend = async () => {
|
||||
setSavingAutoSend(true);
|
||||
try {
|
||||
await axiosConfig.post('/UpdateEmailPreference', null, {
|
||||
params: { installationId, ...autoSend }
|
||||
});
|
||||
setAutoSendDirty(false);
|
||||
setAutoSendSnackbar(intl.formatMessage({ id: 'autoSendSaved', defaultMessage: 'Auto-send preferences saved.' }));
|
||||
} catch {
|
||||
setAutoSendSnackbar(intl.formatMessage({ id: 'autoSendSaveFailed', defaultMessage: 'Failed to save auto-send preferences.' }));
|
||||
} finally {
|
||||
setSavingAutoSend(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hasEmail = !!(installationEmail && installationEmail.trim());
|
||||
|
||||
const fetchReportData = () => {
|
||||
const lang = intl.locale;
|
||||
axiosConfig.get('/GetMonthlyReports', { params: { installationId, language: lang } })
|
||||
|
|
@ -382,36 +423,60 @@ function WeeklyReport({ installationId, installationName }: WeeklyReportProps) {
|
|||
<FormattedMessage id="downloadPdf" defaultMessage="Download PDF" />
|
||||
</Button>
|
||||
)}
|
||||
{tabs[safeTab]?.key !== 'daily' && activeTabHasData && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={regenerating || generating !== null}
|
||||
startIcon={(regenerating || generating !== null) ? <CircularProgress size={16} /> : <RefreshIcon />}
|
||||
onClick={async () => {
|
||||
const key = tabs[safeTab]?.key;
|
||||
if (key === 'weekly') {
|
||||
weeklyRef.current?.regenerate();
|
||||
} else if (key === 'monthly') {
|
||||
const r = monthlyReports[selectedMonthlyIdx];
|
||||
if (r) {
|
||||
setRegenerating(true);
|
||||
try { await handleGenerateMonthly(r.year, r.month); } finally { setRegenerating(false); }
|
||||
}
|
||||
} else if (key === 'yearly') {
|
||||
const r = yearlyReports[selectedYearlyIdx];
|
||||
if (r) {
|
||||
setRegenerating(true);
|
||||
try { await handleGenerateYearly(r.year); } finally { setRegenerating(false); }
|
||||
}
|
||||
}
|
||||
}}
|
||||
sx={{ ml: 1, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
<FormattedMessage id="regenerateReport" defaultMessage="Regenerate" />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2, gap: 1, flexWrap: 'wrap' }} className="no-print">
|
||||
<Typography variant="body2" sx={{ fontWeight: 'bold', color: '#555' }}>
|
||||
<FormattedMessage id="autoSendReports" defaultMessage="Auto-send reports:" />
|
||||
</Typography>
|
||||
<Tooltip title={!hasEmail ? intl.formatMessage({ id: 'autoSendNoEmail', defaultMessage: 'Set email address in Information tab to enable auto-send' }) : ''}>
|
||||
<span>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={autoSend.sendWeekly} onChange={() => handleAutoSendChange('sendWeekly')} disabled={!hasEmail} size="small" />}
|
||||
label={intl.formatMessage({ id: 'weeklyTab', defaultMessage: 'Weekly' })}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title={!hasEmail ? intl.formatMessage({ id: 'autoSendNoEmail', defaultMessage: 'Set email address in Information tab to enable auto-send' }) : ''}>
|
||||
<span>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={autoSend.sendMonthly} onChange={() => handleAutoSendChange('sendMonthly')} disabled={!hasEmail} size="small" />}
|
||||
label={intl.formatMessage({ id: 'monthlyTab', defaultMessage: 'Monthly' })}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title={!hasEmail ? intl.formatMessage({ id: 'autoSendNoEmail', defaultMessage: 'Set email address in Information tab to enable auto-send' }) : ''}>
|
||||
<span>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={autoSend.sendYearly} onChange={() => handleAutoSendChange('sendYearly')} disabled={!hasEmail} size="small" />}
|
||||
label={intl.formatMessage({ id: 'yearlyTab', defaultMessage: 'Yearly' })}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={savingAutoSend ? <CircularProgress size={14} /> : <SaveIcon />}
|
||||
onClick={handleSaveAutoSend}
|
||||
disabled={!autoSendDirty || savingAutoSend}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
<FormattedMessage id="applyChanges" defaultMessage="Save" />
|
||||
</Button>
|
||||
{!hasEmail && (
|
||||
<Typography variant="caption" sx={{ color: '#e67e22', ml: 1 }}>
|
||||
<FormattedMessage id="autoSendNoEmail" defaultMessage="Set email address in Information tab to enable auto-send" />
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Snackbar
|
||||
open={!!autoSendSnackbar}
|
||||
autoHideDuration={4000}
|
||||
onClose={() => setAutoSendSnackbar(null)}
|
||||
message={autoSendSnackbar}
|
||||
/>
|
||||
|
||||
<Box sx={{ display: tabs[safeTab]?.key === 'daily' ? 'block' : 'none', minHeight: '50vh' }}>
|
||||
<DailySection installationId={installationId} onHasData={setDailyHasData} onPeriodChange={(date: string) => setReportPeriod({ start: date, end: date })} />
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export interface I_Installation extends I_S3Credentials {
|
|||
status?: number;
|
||||
serialNumber?: string;
|
||||
networkProvider: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface I_Folder {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"inverterFirmwareVersion": "Wechselrichter-Firmware-Version",
|
||||
"batteryFirmwareVersion": "Batterie-Firmware-Version",
|
||||
"networkProvider": "Netzbetreiber",
|
||||
"emailAddress": "E-Mail-Adresse",
|
||||
"createNewFolder": "Neuer Ordner",
|
||||
"createNewUser": "Neuer Benutzer",
|
||||
"customerName": "Kundenname",
|
||||
|
|
@ -202,6 +203,10 @@
|
|||
"generateMonth": "{month} {year} generieren ({count} Wochen)",
|
||||
"generateYear": "{year} generieren ({count} Monate)",
|
||||
"regenerateReport": "Neu generieren",
|
||||
"autoSendReports": "Berichte automatisch senden:",
|
||||
"autoSendSaved": "Automatische Versandeinstellungen gespeichert.",
|
||||
"autoSendSaveFailed": "Fehler beim Speichern der automatischen Versandeinstellungen.",
|
||||
"autoSendNoEmail": "E-Mail-Adresse im Reiter Information eingeben, um den automatischen Versand zu aktivieren",
|
||||
"generatingMonthly": "Wird generiert...",
|
||||
"generatingYearly": "Wird generiert...",
|
||||
"thisMonthWeeklyReports": "Wöchentliche Berichte dieses Monats",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"inverterFirmwareVersion": "Inverter Firmware Version",
|
||||
"batteryFirmwareVersion": "Battery Firmware Version",
|
||||
"networkProvider": "Network Provider",
|
||||
"emailAddress": "Email Address",
|
||||
"customerName": "Customer name",
|
||||
"english": "English",
|
||||
"german": "German",
|
||||
|
|
@ -184,6 +185,10 @@
|
|||
"generateMonth": "Generate {month} {year} ({count} weeks)",
|
||||
"generateYear": "Generate {year} ({count} months)",
|
||||
"regenerateReport": "Regenerate",
|
||||
"autoSendReports": "Auto-send reports:",
|
||||
"autoSendSaved": "Auto-send preferences saved.",
|
||||
"autoSendSaveFailed": "Failed to save auto-send preferences.",
|
||||
"autoSendNoEmail": "Set email address in Information tab to enable auto-send",
|
||||
"generatingMonthly": "Generating...",
|
||||
"generatingYearly": "Generating...",
|
||||
"thisMonthWeeklyReports": "This Month's Weekly Reports",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"inverterFirmwareVersion": "Version firmware onduleur",
|
||||
"batteryFirmwareVersion": "Version firmware batterie",
|
||||
"networkProvider": "Gestionnaire de réseau",
|
||||
"emailAddress": "Adresse e-mail",
|
||||
"createNewFolder": "Nouveau dossier",
|
||||
"createNewUser": "Nouvel utilisateur",
|
||||
"customerName": "Nom du client",
|
||||
|
|
@ -196,6 +197,10 @@
|
|||
"generateMonth": "Générer {month} {year} ({count} semaines)",
|
||||
"generateYear": "Générer {year} ({count} mois)",
|
||||
"regenerateReport": "Régénérer",
|
||||
"autoSendReports": "Envoi automatique des rapports :",
|
||||
"autoSendSaved": "Préférences d'envoi automatique enregistrées.",
|
||||
"autoSendSaveFailed": "Échec de l'enregistrement des préférences d'envoi automatique.",
|
||||
"autoSendNoEmail": "Définir l'adresse e-mail dans l'onglet Information pour activer l'envoi automatique",
|
||||
"generatingMonthly": "Génération en cours...",
|
||||
"generatingYearly": "Génération en cours...",
|
||||
"thisMonthWeeklyReports": "Rapports hebdomadaires de ce mois",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"inverterFirmwareVersion": "Versione firmware inverter",
|
||||
"batteryFirmwareVersion": "Versione firmware batteria",
|
||||
"networkProvider": "Gestore di rete",
|
||||
"emailAddress": "Indirizzo e-mail",
|
||||
"customerName": "Nome cliente",
|
||||
"english": "Inglese",
|
||||
"german": "Tedesco",
|
||||
|
|
@ -207,6 +208,10 @@
|
|||
"generateMonth": "Genera {month} {year} ({count} settimane)",
|
||||
"generateYear": "Genera {year} ({count} mesi)",
|
||||
"regenerateReport": "Rigenera",
|
||||
"autoSendReports": "Invio automatico rapporti:",
|
||||
"autoSendSaved": "Preferenze di invio automatico salvate.",
|
||||
"autoSendSaveFailed": "Impossibile salvare le preferenze di invio automatico.",
|
||||
"autoSendNoEmail": "Impostare l'indirizzo e-mail nella scheda Informazioni per attivare l'invio automatico",
|
||||
"generatingMonthly": "Generazione in corso...",
|
||||
"generatingYearly": "Generazione in corso...",
|
||||
"thisMonthWeeklyReports": "Rapporti settimanali di questo mese",
|
||||
|
|
|
|||
Loading…
Reference in New Issue