It’s the last part of this expression that is the issue. Right now your expression, if you reduce it down to basic parts is
if all doors are closed return the string 'All doors closed',
but if not then return the string '=loop.door_source.filter(i => items[i.name].state === 'OPEN').length + '/20 doors open'
So, the problem is that the second return value is a string that looks like an expression, not a continuation of the original expression itself (the reason you don’t see that as the result in the label si that you’re missing one more closing ' in the expression so it’s throwing an error instead of finishing the evaluation).
You don’t want this whole return value to be a string, you want it to be the result of the filter method tacked on to a string. So, you don’t need the '...' around the whole return value, and you don’t want the = in front of it. This would be one option:
text: "=(items['gAllDoors'].state === 'CLOSED') ? 'All doors closed' : loop.door_source.filter(i => items[i.name].state === 'OPEN').length + '/20 doors open'"
This is a fairly common use case, however, and there is a typical solution that you may find preferable. OH has a built-in way of handling this easily. Like you I have a group that tells me if any door is open (gPortals) by using an aggregation function. I have a second group that contains all the same door contacts (gPortal_Count), but I use a different aggregation function:
By treating the member base type as a Number the aggregation function gets 0 if a door is closed and 1 if a door is open, so the sum of those numerical states is the count of how many doors are open.
With this setup you don’t need the repeater for your label, it would just looks like this:
- component: Label
config:
text: "=(items['gAllDoors'].state === 'CLOSED') ? 'All doors closed' : items[`gAllDoors_Count`].state + '/20 doors open'"

