@@ -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