Skip to content

Commit 5b7dbdb

Browse files
committed
fix: require tab assignments for widgets
1 parent 4be453c commit 5b7dbdb

1 file changed

Lines changed: 78 additions & 5 deletions

File tree

client/src/components/AdminPanel.jsx

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,46 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
602602
}
603603
};
604604

605+
const getMissingEnabledCoreWidgetAssignments = (settingsToValidate, assignmentsToValidate) => {
606+
const normalizedSettings = normalizeWidgetSettings(settingsToValidate);
607+
608+
return Object.keys(DEFAULT_WIDGET_SETTINGS).filter((widgetName) => {
609+
const isEnabled = Boolean(normalizedSettings?.[widgetName]?.enabled);
610+
const selectedTabNumbers = assignmentsToValidate?.[widgetName];
611+
return isEnabled && (!Array.isArray(selectedTabNumbers) || selectedTabNumbers.length === 0);
612+
});
613+
};
614+
615+
const getMissingEnabledPluginAssignments = (pluginSettingsToValidate, assignmentsToValidate, uploadedWidgetsToValidate) => {
616+
const uploadedPluginFilenames = new Set((uploadedWidgetsToValidate || []).map((widget) => widget.filename));
617+
618+
return Object.entries(pluginSettingsToValidate || {}).reduce((missing, [filename, config]) => {
619+
if (!uploadedPluginFilenames.has(filename)) {
620+
return missing;
621+
}
622+
623+
if (!config?.enabled) {
624+
return missing;
625+
}
626+
627+
const pluginWidgetName = `plugin:${filename}`;
628+
const selectedTabNumbers = assignmentsToValidate?.[pluginWidgetName];
629+
if (!Array.isArray(selectedTabNumbers) || selectedTabNumbers.length === 0) {
630+
missing.push(pluginWidgetName);
631+
}
632+
633+
return missing;
634+
}, []);
635+
};
636+
605637
const saveWidgetSettings = async () => {
638+
const missingEnabledWidgets = getMissingEnabledCoreWidgetAssignments(widgetSettings, widgetAssignments);
639+
if (missingEnabledWidgets.length > 0) {
640+
setSaveMessage({ show: true, type: 'error', text: 'Each enabled widget must have at least one tab selected.' });
641+
setTimeout(() => setSaveMessage({ show: false, type: '', text: '' }), 4000);
642+
return;
643+
}
644+
606645
setIsLoading(true);
607646
try {
608647
const normalizedWidgetSettings = normalizeWidgetSettings(widgetSettings);
@@ -648,6 +687,13 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
648687
};
649688

650689
const savePluginSettings = async () => {
690+
const missingEnabledPlugins = getMissingEnabledPluginAssignments(pluginSettings, pluginAssignments, uploadedWidgets);
691+
if (missingEnabledPlugins.length > 0) {
692+
setSaveMessage({ show: true, type: 'error', text: 'Each enabled plugin must have at least one tab selected.' });
693+
setTimeout(() => setSaveMessage({ show: false, type: '', text: '' }), 4000);
694+
return;
695+
}
696+
651697
setIsLoading(true);
652698
try {
653699
await patchDeviceSettings({ pluginSettings });
@@ -1671,6 +1717,8 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
16711717
? backendStats.repository
16721718
: DEFAULT_HOMEGLOW_REPOSITORY;
16731719
const backendCommitUrl = buildCommitUrl(backendRepository, backendStats?.commit);
1720+
const weatherHasRequiredTabsError = Boolean(widgetSettings.weather?.enabled)
1721+
&& (!Array.isArray(widgetAssignments.weather) || widgetAssignments.weather.length === 0);
16741722

16751723
if (checkingPin) {
16761724
return (
@@ -1728,6 +1776,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
17281776
{widgetsSubTab === 0 && (
17291777
<Box
17301778
component="form"
1779+
noValidate
17311780
onSubmit={(event) => {
17321781
event.preventDefault();
17331782
saveWidgetSettings();
@@ -1739,7 +1788,10 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
17391788

17401789
{Object.entries(widgetSettings).filter(([key]) =>
17411790
['chores', 'calendar', 'photos'].includes(key)
1742-
).map(([widget, config]) => (
1791+
).map(([widget, config]) => {
1792+
const hasRequiredTabsError = Boolean(config.enabled) && (!Array.isArray(widgetAssignments[widget]) || widgetAssignments[widget].length === 0);
1793+
1794+
return (
17431795
<Box key={widget} sx={{ mb: 3, p: 2, border: '1px solid var(--card-border)', borderRadius: 1 }}>
17441796
<Typography variant="subtitle1" sx={{ mb: 2, textTransform: 'capitalize', fontWeight: 'bold' }}>
17451797
{widget} Widget
@@ -1805,8 +1857,14 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
18051857
<TextField
18061858
{...params}
18071859
label="Show on Tabs"
1860+
required={Boolean(config.enabled)}
1861+
error={hasRequiredTabsError}
18081862
placeholder="Select tabs..."
1809-
helperText="Select which tabs this widget should appear on (defaults to Home tab if none selected)"
1863+
helperText={
1864+
hasRequiredTabsError
1865+
? 'Required: select at least one tab when this widget is enabled.'
1866+
: 'Select which tabs this widget should appear on.'
1867+
}
18101868
/>
18111869
)}
18121870
/>
@@ -1818,7 +1876,8 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
18181876
</Alert>
18191877
)}
18201878
</Box>
1821-
))}
1879+
);
1880+
})}
18221881

18231882
<Box sx={{ mb: 3, p: 2, border: '2px solid var(--accent)', borderRadius: 1, backgroundColor: 'rgba(158, 127, 255, 0.05)' }}>
18241883
<Typography variant="subtitle1" sx={{ mb: 2, fontWeight: 'bold' }}>
@@ -1885,8 +1944,14 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
18851944
<TextField
18861945
{...params}
18871946
label="Show on Tabs"
1947+
required={Boolean(widgetSettings.weather?.enabled)}
1948+
error={weatherHasRequiredTabsError}
18881949
placeholder="Select tabs..."
1889-
helperText="Select which tabs this widget should appear on (defaults to Home tab if none selected)"
1950+
helperText={
1951+
weatherHasRequiredTabsError
1952+
? 'Required: select at least one tab when this widget is enabled.'
1953+
: 'Select which tabs this widget should appear on.'
1954+
}
18901955
/>
18911956
)}
18921957
/>
@@ -1982,6 +2047,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
19822047
{uploadedWidgets.length > 0 && (
19832048
<Box
19842049
component="form"
2050+
noValidate
19852051
onSubmit={(event) => {
19862052
event.preventDefault();
19872053
savePluginSettings();
@@ -1996,6 +2062,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
19962062
{uploadedWidgets.map((plugin) => {
19972063
const pSettings = pluginSettings[plugin.filename] || {};
19982064
const pluginWidgetName = `plugin:${plugin.filename}`;
2065+
const hasRequiredTabsError = Boolean(pSettings.enabled) && (!Array.isArray(pluginAssignments[pluginWidgetName]) || pluginAssignments[pluginWidgetName].length === 0);
19992066
return (
20002067
<Box key={plugin.filename} sx={{ mb: 3, p: 2, border: '1px solid var(--card-border)', borderRadius: 1 }}>
20012068
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
@@ -2090,8 +2157,14 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
20902157
<TextField
20912158
{...params}
20922159
label="Show on Tabs"
2160+
required={Boolean(pSettings.enabled)}
2161+
error={hasRequiredTabsError}
20932162
placeholder="Select tabs..."
2094-
helperText="Select which tabs this plugin should appear on (defaults to Home tab if none selected)"
2163+
helperText={
2164+
hasRequiredTabsError
2165+
? 'Required: select at least one tab when this plugin is enabled.'
2166+
: 'Select which tabs this plugin should appear on.'
2167+
}
20952168
/>
20962169
)}
20972170
/>

0 commit comments

Comments
 (0)