feat(mobile): wire review note input to inbox approve/reject
- note-agent-actions API: updateApprovalState now accepts optional reviewNote - inbox-store: approve/reject actions pass reviewNote through to backend - Inbox screen: TextInput for review note above approval cards - Review note cleared after successful approve/reject Verification: mobile typecheck passes.
This commit is contained in:
parent
12d90098eb
commit
f8a4c18f27
@ -114,6 +114,7 @@ export async function updateApprovalState(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
state: 'approved' | 'rejected',
|
||||
reviewNote?: string,
|
||||
): Promise<MobileApprovalItem> {
|
||||
const updated = await getApiClient().fetch<NoteAgentActionDoc>(
|
||||
`/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
|
||||
@ -122,6 +123,7 @@ export async function updateApprovalState(
|
||||
body: JSON.stringify({
|
||||
state,
|
||||
reviewedAt: new Date().toISOString(),
|
||||
...(reviewNote ? { reviewNote } : {}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import { useInboxStore, type ActivityItem, type ApprovalItem, type InboxState } from '../../store/inbox-store';
|
||||
import { colors } from '../../theme';
|
||||
|
||||
@ -11,6 +11,7 @@ export default function InboxScreen() {
|
||||
const approve = useInboxStore((state: InboxState) => state.approve);
|
||||
const reject = useInboxStore((state: InboxState) => state.reject);
|
||||
const [pendingApprovalId, setPendingApprovalId] = useState<string | null>(null);
|
||||
const [reviewNote, setReviewNote] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
void hydrate();
|
||||
@ -21,6 +22,17 @@ export default function InboxScreen() {
|
||||
<Text style={styles.title}>Inbox</Text>
|
||||
<Text style={styles.body}>Review simple agent actions and approve or reject them from mobile.</Text>
|
||||
{isLoading ? <Text style={styles.empty}>Loading approvals…</Text> : null}
|
||||
{approvals.length > 0 ? (
|
||||
<TextInput
|
||||
style={styles.reviewNoteInput}
|
||||
placeholder="Optional review note…"
|
||||
placeholderTextColor={colors.textSecondary}
|
||||
value={reviewNote}
|
||||
onChangeText={setReviewNote}
|
||||
multiline
|
||||
numberOfLines={2}
|
||||
/>
|
||||
) : null}
|
||||
{approvals.map((item: ApprovalItem) => (
|
||||
<View key={item.id} style={styles.card}>
|
||||
<Text style={styles.cardTitle}>{item.title}</Text>
|
||||
@ -36,7 +48,9 @@ export default function InboxScreen() {
|
||||
onPress={async () => {
|
||||
setPendingApprovalId(item.id);
|
||||
try {
|
||||
await approve(item.id);
|
||||
const note = reviewNote.trim() || undefined;
|
||||
await approve(item.id, note);
|
||||
setReviewNote('');
|
||||
} finally {
|
||||
setPendingApprovalId(null);
|
||||
}
|
||||
@ -55,7 +69,9 @@ export default function InboxScreen() {
|
||||
onPress={async () => {
|
||||
setPendingApprovalId(item.id);
|
||||
try {
|
||||
await reject(item.id);
|
||||
const note = reviewNote.trim() || undefined;
|
||||
await reject(item.id, note);
|
||||
setReviewNote('');
|
||||
} finally {
|
||||
setPendingApprovalId(null);
|
||||
}
|
||||
@ -181,4 +197,15 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '700',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
reviewNoteInput: {
|
||||
backgroundColor: colors.bgElevated,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.borderDefault,
|
||||
color: colors.textPrimary,
|
||||
padding: 12,
|
||||
fontSize: 14,
|
||||
minHeight: 48,
|
||||
textAlignVertical: 'top' as const,
|
||||
},
|
||||
});
|
||||
|
||||
@ -28,8 +28,8 @@ export type InboxState = {
|
||||
activity: ActivityItem[];
|
||||
isLoading: boolean;
|
||||
hydrate: () => Promise<void>;
|
||||
approve: (id: string) => Promise<void>;
|
||||
reject: (id: string) => Promise<void>;
|
||||
approve: (id: string, reviewNote?: string) => Promise<void>;
|
||||
reject: (id: string, reviewNote?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
function toApprovalItem(item: MobileApprovalItem): ApprovalItem {
|
||||
@ -53,26 +53,26 @@ export const useInboxStore = create<InboxState>((set, get) => ({
|
||||
isLoading: false,
|
||||
});
|
||||
},
|
||||
async approve(id: string) {
|
||||
async approve(id: string, reviewNote?: string) {
|
||||
const current = get().approvals.find((item) => item.id === id);
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = await updateApprovalState(id, current.workspaceId, 'approved');
|
||||
const updated = await updateApprovalState(id, current.workspaceId, 'approved', reviewNote);
|
||||
set((state) => ({
|
||||
approvals: state.approvals.map((item) =>
|
||||
item.id === id ? toApprovalItem(updated) : item,
|
||||
),
|
||||
}));
|
||||
},
|
||||
async reject(id: string) {
|
||||
async reject(id: string, reviewNote?: string) {
|
||||
const current = get().approvals.find((item) => item.id === id);
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = await updateApprovalState(id, current.workspaceId, 'rejected');
|
||||
const updated = await updateApprovalState(id, current.workspaceId, 'rejected', reviewNote);
|
||||
set((state) => ({
|
||||
approvals: state.approvals.map((item) =>
|
||||
item.id === id ? toApprovalItem(updated) : item,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user