feat(app): Projekte verstecken auch in der App (Auge + Toggle)
Spiegelt das Diagnostic-Feature auf App-Seite: - Project-Interface + updateProject um hidden erweitert; setProjectHidden(). - ProjectsBrowser: versteckte Projekte standardmaessig ausgeblendet (mama sieht sie nicht). Auge pro Zeile (🙈 verstecken / 👁 sichtbar), Toggle "👁 Versteckte anzeigen (N)" blendet sie temporaer gedimmt + Badge ein zum Ansehen/Auswaehlen/Wieder-Sichtbarmachen. Eigener Touch am Auge, damit der Tap nicht das Projekt wechselt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,9 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
const [editing, setEditing] = useState<Project | null>(null);
|
const [editing, setEditing] = useState<Project | null>(null);
|
||||||
const [editName, setEditName] = useState('');
|
const [editName, setEditName] = useState('');
|
||||||
const [editDesc, setEditDesc] = useState('');
|
const [editDesc, setEditDesc] = useState('');
|
||||||
|
// Versteckte Projekte standardmaessig ausblenden; Toggle blendet sie
|
||||||
|
// temporaer (gedimmt) ein — zum Ansehen/Auswaehlen oder Wieder-Sichtbarmachen.
|
||||||
|
const [showHidden, setShowHidden] = useState(false);
|
||||||
|
|
||||||
// Refs damit useCallback NICHT bei jeder Re-Render des Parents neu erzeugt
|
// Refs damit useCallback NICHT bei jeder Re-Render des Parents neu erzeugt
|
||||||
// wird (parent uebergibt oft inline-arrow-Callbacks, neue Identity jedes
|
// wird (parent uebergibt oft inline-arrow-Callbacks, neue Identity jedes
|
||||||
@@ -158,6 +161,12 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
]);
|
]);
|
||||||
}, [load]);
|
}, [load]);
|
||||||
|
|
||||||
|
const toggleHidden = useCallback((p: Project) => {
|
||||||
|
brainApi.setProjectHidden(p.id, !p.hidden)
|
||||||
|
.then(() => load())
|
||||||
|
.catch(e => Alert.alert('Fehler', String(e?.message || e)));
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
const archiveProject = useCallback((p: Project) => {
|
const archiveProject = useCallback((p: Project) => {
|
||||||
Alert.alert(`"${p.name}" archivieren?`,
|
Alert.alert(`"${p.name}" archivieren?`,
|
||||||
'Verschwindet aus der Standardliste. Über "archivierte zeigen" erreichbar.',
|
'Verschwindet aus der Standardliste. Über "archivierte zeigen" erreichbar.',
|
||||||
@@ -176,11 +185,12 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
const renderItem = ({ item }: { item: Project }) => {
|
const renderItem = ({ item }: { item: Project }) => {
|
||||||
const isActive = item.id === activeId;
|
const isActive = item.id === activeId;
|
||||||
const dot = _statusDot(item.id);
|
const dot = _statusDot(item.id);
|
||||||
|
const hidden = !!item.hidden;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => switchTo(item.id)}
|
onPress={() => switchTo(item.id)}
|
||||||
onLongPress={() => openEdit(item)}
|
onLongPress={() => openEdit(item)}
|
||||||
style={[s.row, isActive && s.rowActive]}
|
style={[s.row, isActive && s.rowActive, hidden && s.rowHidden]}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||||
@@ -188,6 +198,7 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: dot.color }} />
|
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: dot.color }} />
|
||||||
)}
|
)}
|
||||||
<Text style={[s.rowName, isActive && { color: '#34C759' }]}>{item.name}</Text>
|
<Text style={[s.rowName, isActive && { color: '#34C759' }]}>{item.name}</Text>
|
||||||
|
{hidden && <Text style={s.hiddenBadge}>versteckt</Text>}
|
||||||
{item.status === 'ended' && <Text style={s.statusBadge}>beendet</Text>}
|
{item.status === 'ended' && <Text style={s.statusBadge}>beendet</Text>}
|
||||||
{isActive && <Text style={s.activeBadge}>✓ FOCUS</Text>}
|
{isActive && <Text style={s.activeBadge}>✓ FOCUS</Text>}
|
||||||
</View>
|
</View>
|
||||||
@@ -199,10 +210,22 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
{dot.label ? ` · ${dot.label}` : ''}
|
{dot.label ? ` · ${dot.label}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
{/* Auge: verstecken (🙈) / wieder sichtbar (👁). Eigener Touch, damit
|
||||||
|
der Tap NICHT das Projekt wechselt. */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => toggleHidden(item)}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
style={s.eyeBtn}
|
||||||
|
>
|
||||||
|
<Text style={s.eyeIcon}>{hidden ? '👁' : '🙈'}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hiddenCount = projects.filter(p => p.hidden).length;
|
||||||
|
const visibleProjects = showHidden ? projects : projects.filter(p => !p.hidden);
|
||||||
|
|
||||||
const body = (
|
const body = (
|
||||||
<View style={{ flex: 1, backgroundColor: '#0A0A14' }}>
|
<View style={{ flex: 1, backgroundColor: '#0A0A14' }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -243,6 +266,17 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
|
{/* Versteckte-Toggle — nur wenn es welche gibt (oder gerade eingeblendet) */}
|
||||||
|
{(hiddenCount > 0 || showHidden) && (
|
||||||
|
<TouchableOpacity onPress={() => setShowHidden(v => !v)} style={s.hiddenToggle}>
|
||||||
|
<Text style={s.hiddenToggleText}>
|
||||||
|
{showHidden
|
||||||
|
? `🙈 Versteckte ausblenden${hiddenCount ? ` (${hiddenCount})` : ''}`
|
||||||
|
: `👁 Versteckte anzeigen${hiddenCount ? ` (${hiddenCount})` : ''}`}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<View style={{ padding: 24, alignItems: 'center' }}>
|
<View style={{ padding: 24, alignItems: 'center' }}>
|
||||||
<ActivityIndicator color="#0096FF" />
|
<ActivityIndicator color="#0096FF" />
|
||||||
@@ -251,14 +285,21 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
|
|||||||
<Text style={s.errorText}>⚠ {err}</Text>
|
<Text style={s.errorText}>⚠ {err}</Text>
|
||||||
) : (
|
) : (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={projects}
|
data={visibleProjects}
|
||||||
keyExtractor={p => p.id}
|
keyExtractor={p => p.id}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
|
projects.length > 0 ? (
|
||||||
|
<Text style={s.emptyText}>
|
||||||
|
Alle {hiddenCount} Projekte sind versteckt.{'\n'}
|
||||||
|
Tipp „👁 Versteckte anzeigen".
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
<Text style={s.emptyText}>
|
<Text style={s.emptyText}>
|
||||||
Noch keine Projekte. Tipp + Neu oder sag zu ARIA:{'\n'}
|
Noch keine Projekte. Tipp + Neu oder sag zu ARIA:{'\n'}
|
||||||
„Lass uns ein Projekt 'XY' anlegen".
|
„Lass uns ein Projekt 'XY' anlegen".
|
||||||
</Text>
|
</Text>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -365,6 +406,8 @@ const s = StyleSheet.create({
|
|||||||
headerBtnText: { color: '#0096FF', fontSize: 18, fontWeight: '600' },
|
headerBtnText: { color: '#0096FF', fontSize: 18, fontWeight: '600' },
|
||||||
headerTitle: { flex: 1, textAlign: 'center', color: '#E0E0F0', fontSize: 18, fontWeight: '700' },
|
headerTitle: { flex: 1, textAlign: 'center', color: '#E0E0F0', fontSize: 18, fontWeight: '700' },
|
||||||
row: {
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
@@ -375,6 +418,18 @@ const s = StyleSheet.create({
|
|||||||
borderLeftWidth: 3,
|
borderLeftWidth: 3,
|
||||||
borderLeftColor: '#34C759',
|
borderLeftColor: '#34C759',
|
||||||
},
|
},
|
||||||
|
rowHidden: { opacity: 0.55 },
|
||||||
|
eyeBtn: { paddingHorizontal: 8, paddingVertical: 6, marginLeft: 6 },
|
||||||
|
eyeIcon: { fontSize: 18 },
|
||||||
|
hiddenBadge: { color: '#B392F0', fontSize: 10, fontWeight: '700',
|
||||||
|
backgroundColor: 'rgba(179,146,240,0.15)', paddingHorizontal: 6,
|
||||||
|
paddingVertical: 2, borderRadius: 4 },
|
||||||
|
hiddenToggle: {
|
||||||
|
paddingHorizontal: 16, paddingVertical: 10,
|
||||||
|
borderBottomWidth: 1, borderColor: '#1E1E2E',
|
||||||
|
backgroundColor: '#0D0D18',
|
||||||
|
},
|
||||||
|
hiddenToggleText: { color: '#B392F0', fontSize: 12, fontWeight: '600' },
|
||||||
rowName: { color: '#E0E0F0', fontSize: 16, fontWeight: '600' },
|
rowName: { color: '#E0E0F0', fontSize: 16, fontWeight: '600' },
|
||||||
rowDesc: { color: '#8888AA', fontSize: 13, marginTop: 4 },
|
rowDesc: { color: '#8888AA', fontSize: 13, marginTop: 4 },
|
||||||
rowMeta: { color: '#555570', fontSize: 11, marginTop: 4 },
|
rowMeta: { color: '#555570', fontSize: 11, marginTop: 4 },
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ export interface Project {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: 'active' | 'ended' | 'archived';
|
status: 'active' | 'ended' | 'archived';
|
||||||
|
hidden?: boolean; // aus Listen ausgeblendet (bleibt nutzbar)
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
last_activity_at: number;
|
last_activity_at: number;
|
||||||
@@ -593,14 +594,22 @@ export const brainApi = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Projekt-Metadaten patchen (name / description). */
|
/** Projekt-Metadaten patchen (name / description / hidden). */
|
||||||
updateProject(projectId: string, patch: Partial<Pick<Project, 'name' | 'description'>>): Promise<Project> {
|
updateProject(projectId: string, patch: Partial<Pick<Project, 'name' | 'description' | 'hidden'>>): Promise<Project> {
|
||||||
return _send(`/projects/${encodeURIComponent(projectId)}`, {
|
return _send(`/projects/${encodeURIComponent(projectId)}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: patch,
|
body: patch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Projekt verstecken / wieder sichtbar machen (bleibt voll nutzbar). */
|
||||||
|
setProjectHidden(projectId: string, hidden: boolean): Promise<Project> {
|
||||||
|
return _send(`/projects/${encodeURIComponent(projectId)}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: { hidden },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/** Queue-Status: pro Kontext (project_id oder __main__ fuer Hauptchat)
|
/** Queue-Status: pro Kontext (project_id oder __main__ fuer Hauptchat)
|
||||||
* ob gerade ein Request in Verarbeitung ist + wieviele in der Queue warten.
|
* ob gerade ein Request in Verarbeitung ist + wieviele in der Queue warten.
|
||||||
* Wird fuer Status-Dots im Drawer periodisch gepollt. */
|
* Wird fuer Status-Dots im Drawer periodisch gepollt. */
|
||||||
|
|||||||
Reference in New Issue
Block a user