@@ -7,33 +7,37 @@ import { ThemedText } from "../../components/ThemedText";
77import type { BallotSummary , ElectionDetails , IElectionEngine } from "@votetorrent/vote-core" ;
88import { globalStyles } from "../../theme/styles" ;
99import { ElectionDetailsBlock } from "./components/ElectionDetailsBlock" ;
10+ import { ElectionTimelineList } from "./components/ElectionTimelineList" ;
1011import { ChipButton } from "../../components/ChipButton" ;
1112import { KeyholderCard } from "./components/KeyholderCard" ;
1213import { CustomButton } from "../../components/CustomButton" ;
14+ import { CustomTextInput } from "../../components/CustomTextInput" ;
1315import { InfoCard } from "../../components/InfoCard" ;
14- import { Timeline } from "./components/Timeline" ;
1516import { formatDate } from "../../utils/displayUtils" ;
1617import type { NavigationProp } from "../../navigation/types" ;
1718
1819/**
19- * ElectionDetailsScreen — timeline-focal stacked layout per Phase 9 D-07 .
20+ * ElectionDetailsScreen — Figma parity #13/#18/#19 per Phase 9 plan 09-14 .
2021 *
21- * Order (top → bottom):
22- * 1. Header (title + metadata via ElectionDetailsBlock)
23- * 2. Timeline (vertical, 5 milestones with status dots) — focal section
24- * 3. Keyholders (compact list)
25- * 4. Tags (chip row)
26- * 5. Revision deadline (callout)
27- * 6. Revise / Clone actions
28- * 7. Ballot templates list — G2/G12 (09-10): loaded from electionEngine.getBallots()
29- * via useFocusEffect; one card per template when >=1 exists; empty-state
30- * (noBallotYet text + CREATE BALLOT TEMPLATE button) shown only when 0 templates.
22+ * Render order (top → bottom):
23+ * 1. ElectionDetailsBlock — immutable core (title, Authority, Type, Date-time, Core Sig)
24+ * 2. Current revision section (Revision #N + date, Tags, Timeline text list,
25+ * Keyholder Policy, Revision Signature, PREVIEW chip)
26+ * 3. Keyholders — Sent/Unsent cards + chevron
27+ * 4. REVISE ELECTION / CLONE ELECTION actions
28+ * 5. Proposed Revision block (conditional — only when electionDetails.proposed exists)
29+ * · revision header, tags, timeline text list, keyholder policy
30+ * · signing rows per keyholder (SIGN accent / SHARE warning CustomButton pills)
31+ * · ADJUST REVISION → EditElectionRevision
32+ * 6. Ballot Templates section (one InfoCard per template with Questions subtitle)
33+ * 7. More section (collapsible) + filter-authorities input
3134 */
3235export default function ElectionDetailsScreen ( ) {
3336 const { t } = useTranslation ( ) ;
3437 const { electionEngine } = useRoute ( ) . params as { electionEngine : IElectionEngine } ;
3538 const [ electionDetails , setElectionDetails ] = useState < ElectionDetails | null > ( null ) ;
3639 const [ ballots , setBallots ] = useState < BallotSummary [ ] > ( [ ] ) ;
40+ const [ moreOpen , setMoreOpen ] = useState ( false ) ;
3741 const { colors } = useTheme ( ) as ExtendedTheme ;
3842 const navigation = useNavigation < NavigationProp > ( ) ;
3943 const insets = useSafeAreaInsets ( ) ;
@@ -79,25 +83,57 @@ export default function ElectionDetailsScreen() {
7983 ) ;
8084 }
8185
86+ const { election, current, proposed } = electionDetails ;
87+ const revisionSignature = ( current as any ) . signature ?. signature as string | undefined ;
88+ const revisionDate = Array . isArray ( current . revisionTimestamp ) && current . revisionTimestamp . length > 0
89+ ? ( current . revisionTimestamp [ 0 ] as unknown as number )
90+ : election . date ;
91+
8292 return (
8393 < ScrollView
8494 style = { styles . container }
8595 contentContainerStyle = { { paddingBottom : insets . bottom + 24 } } >
86- { /* Header + immutable metadata */ }
96+
97+ { /* 1. Immutable core block (title + Authority/Type/Date + Core Signature) */ }
8798 < View style = { styles . section } >
8899 < ElectionDetailsBlock electionDetails = { electionDetails } />
89100 </ View >
90101
91- { /* Timeline — focal section per D-07 */ }
102+ { /* 2. Current revision section — rendered ONCE here */ }
92103 < View style = { styles . section } >
93- < ThemedText type = "defaultSemiBold" > { t ( "timeline" ) } </ ThemedText >
94- < Timeline electionDetails = { electionDetails } />
104+ < View style = { styles . detail } >
105+ < ThemedText type = "defaultSemiBold" > { t ( "revision" ) } : </ ThemedText >
106+ < ThemedText > #{ current . revision } - { formatDate ( revisionDate ) } </ ThemedText >
107+ </ View >
108+ < View style = { styles . detail } >
109+ < ThemedText type = "defaultSemiBold" > { t ( "tags" ) } : </ ThemedText >
110+ < ThemedText > { current . tags . join ( ", " ) } </ ThemedText >
111+ </ View >
112+
113+ { /* Timeline — text list per Decision 2 */ }
114+ < ThemedText type = "defaultSemiBold" style = { styles . sectionLabel } > { t ( "timeline" ) } </ ThemedText >
115+ < ElectionTimelineList timeline = { current . timeline } />
116+
117+ < View style = { styles . detail } >
118+ < ThemedText type = "defaultSemiBold" > { t ( "keyholderPolicy" ) } : </ ThemedText >
119+ < ThemedText > { current . keyholderThreshold } of { current . keyholders . length } </ ThemedText >
120+ </ View >
121+
122+ { revisionSignature ? (
123+ < View style = { styles . detail } >
124+ < ThemedText type = "defaultSemiBold" > { t ( "revisionSignature" ) } : </ ThemedText >
125+ < ThemedText numberOfLines = { 1 } ellipsizeMode = "middle" > { revisionSignature } </ ThemedText >
126+ </ View >
127+ ) : null }
128+
129+ { /* PREVIEW chip — stub */ }
130+ < ChipButton label = { t ( "previewBallots" ) } onPress = { ( ) => console . log ( "preview-stub" ) } />
95131 </ View >
96132
97- { /* Keyholders */ }
133+ { /* 3. Keyholders — Sent/Unsent + chevron */ }
98134 < View style = { styles . section } >
99135 < ThemedText type = "defaultSemiBold" > { t ( "keyholders" ) } </ ThemedText >
100- { electionDetails . current . keyholders . map ( ( keyholder , index ) => (
136+ { current . keyholders . map ( ( keyholder , index ) => (
101137 < KeyholderCard
102138 key = { keyholder . invite ?. name ?? `keyholder-${ index } ` }
103139 invitationStatus = { keyholder }
@@ -106,57 +142,95 @@ export default function ElectionDetailsScreen() {
106142 ) ) }
107143 </ View >
108144
109- { /* Tags chip row */ }
110- < View style = { styles . section } >
111- < ThemedText type = "defaultSemiBold" > { t ( "tags" ) } </ ThemedText >
112- < View style = { styles . tagRow } >
113- { electionDetails . current . tags . map ( ( tag ) => (
114- < ChipButton key = { tag } label = { tag } />
115- ) ) }
116- </ View >
117- </ View >
118-
119- { /* Revision deadline callout */ }
120- < View style = { [ styles . section , styles . calloutBox , { backgroundColor : colors . card , borderColor : colors . border } ] } >
121- < ThemedText type = "defaultSemiBold" > { t ( "revisionDeadline" ) } </ ThemedText >
122- < ThemedText > { formatDate ( electionDetails . election . revisionDeadline ) } </ ThemedText >
123- </ View >
124-
125- { /* Revise / Clone actions */ }
145+ { /* 4. REVISE / CLONE actions */ }
126146 < View style = { styles . section } >
127147 < CustomButton
128148 title = { t ( "reviseElection" ) }
129149 size = "thin"
130150 icon = "pencil"
131151 backgroundColor = { colors . accent }
132- onPress = { ( ) => { } }
152+ onPress = { ( ) => navigation . navigate ( "EditElectionRevision" , { electionEngine } ) }
133153 />
134154 < CustomButton
135155 title = { t ( "cloneElection" ) }
136156 size = "thin"
137157 icon = "copy"
138158 backgroundColor = { colors . accent }
139- onPress = { ( ) => { } }
159+ onPress = { ( ) => console . log ( "clone-stub" ) }
140160 />
141161 </ View >
142162
143- { /* Ballot Templates section — G1/G2/G12 (09-10):
144- Show one InfoCard per template when >=1 exists (each navigates EditBallot
145- with ballotId+electionEngine for upsert). When 0 templates, show only
146- the empty-state text + CREATE BALLOT TEMPLATE button (G1 label). */ }
163+ { /* 5. Proposed Revision block — conditional */ }
164+ { electionDetails . proposed && (
165+ < View style = { styles . section } >
166+ < ThemedText type = "defaultSemiBold" > { t ( "proposedRevisionHeader" ) } </ ThemedText >
167+
168+ < View style = { styles . detail } >
169+ < ThemedText type = "defaultSemiBold" > { t ( "revision" ) } : </ ThemedText >
170+ < ThemedText > #{ proposed ! . proposed . revision } </ ThemedText >
171+ </ View >
172+ < View style = { styles . detail } >
173+ < ThemedText type = "defaultSemiBold" > { t ( "tags" ) } : </ ThemedText >
174+ < ThemedText > { proposed ! . proposed . tags . join ( ", " ) } </ ThemedText >
175+ </ View >
176+
177+ < ThemedText type = "defaultSemiBold" style = { styles . sectionLabel } > { t ( "timeline" ) } </ ThemedText >
178+ < ElectionTimelineList timeline = { proposed ! . proposed . timeline } />
179+
180+ < View style = { styles . detail } >
181+ < ThemedText type = "defaultSemiBold" > { t ( "keyholderPolicy" ) } : </ ThemedText >
182+ < ThemedText > { proposed ! . proposed . keyholderThreshold } of { proposed ! . proposed . keyholders . length } </ ThemedText >
183+ </ View >
184+
185+ { /* Signing rows — one per proposed keyholder */ }
186+ { proposed ! . proposed . keyholders . map ( ( holder , idx ) => (
187+ < View key = { holder . name ?? `proposed-holder-${ idx } ` } style = { styles . signingRow } >
188+ < ThemedText type = "defaultSemiBold" style = { styles . holderName } > { holder . name } </ ThemedText >
189+ < View style = { styles . signingPills } >
190+ < CustomButton
191+ title = { t ( "signRevision" ) }
192+ size = "thin"
193+ icon = "signature"
194+ backgroundColor = { colors . accent }
195+ onPress = { ( ) => console . log ( "sign-stub" ) }
196+ />
197+ < CustomButton
198+ title = { t ( "shareRevision" ) }
199+ size = "thin"
200+ icon = "share-nodes"
201+ backgroundColor = { colors . warning }
202+ onPress = { ( ) => console . log ( "share-stub" ) }
203+ />
204+ </ View >
205+ </ View >
206+ ) ) }
207+
208+ { /* ADJUST REVISION → EditElectionRevision */ }
209+ < CustomButton
210+ title = { t ( "adjustRevision" ) }
211+ size = "thin"
212+ icon = "pencil"
213+ backgroundColor = { colors . accent }
214+ onPress = { ( ) => navigation . navigate ( "EditElectionRevision" , { electionEngine } ) }
215+ />
216+ </ View >
217+ ) }
218+
219+ { /* 6. Ballot Templates section */ }
147220 < View style = { styles . section } >
148221 < ThemedText type = "title" > { t ( "ballotTemplates" ) } </ ThemedText >
149222 { ballots . length > 0 ? (
150223 ballots . map ( ( ballot ) => (
151224 < InfoCard
152225 key = { ballot . id }
153226 title = { ballot . authorityId || t ( "ballotTemplate" ) }
227+ subtitle = { t ( "questionsLabel" ) + ": —" }
154228 icon = "chevron-right"
155229 onPress = { ( ) =>
156230 navigation . navigate ( "EditBallot" , {
157- electionId : electionDetails . election . id ,
158- electionTitle : electionDetails . election . title ,
159- electionDate : formatDate ( electionDetails . election . revisionDeadline ) ,
231+ electionId : election . id ,
232+ electionTitle : election . title ,
233+ electionDate : formatDate ( election . revisionDeadline ) ,
160234 ballotId : ballot . id ,
161235 electionEngine,
162236 } as any )
@@ -173,31 +247,49 @@ export default function ElectionDetailsScreen() {
173247 backgroundColor = { colors . accent }
174248 onPress = { ( ) =>
175249 navigation . navigate ( "CreateBallot" , {
176- electionId : electionDetails . election . id ,
177- electionTitle : electionDetails . election . title ,
178- electionDate : formatDate ( electionDetails . election . revisionDeadline ) ,
250+ electionId : election . id ,
251+ electionTitle : election . title ,
252+ electionDate : formatDate ( election . revisionDeadline ) ,
179253 electionEngine,
180254 } as any )
181255 }
182256 />
183257 </ >
184258 ) }
185259 </ View >
260+
261+ { /* 7. More section (collapsible) + filter-authorities input */ }
262+ < View style = { styles . section } >
263+ < ChipButton label = { t ( "more" ) } onPress = { ( ) => setMoreOpen ( ( v ) => ! v ) } />
264+ { moreOpen && (
265+ < CustomTextInput
266+ placeholder = { t ( "filterAuthoritiesField" ) }
267+ />
268+ ) }
269+ </ View >
186270 </ ScrollView >
187271 ) ;
188272}
189273
190274const localStyles = StyleSheet . create ( {
191- tagRow : {
275+ detail : {
192276 flexDirection : "row" ,
193277 flexWrap : "wrap" ,
194- gap : 8 ,
278+ marginVertical : 2 ,
279+ } ,
280+ sectionLabel : {
195281 marginTop : 8 ,
282+ marginBottom : 2 ,
196283 } ,
197- calloutBox : {
198- padding : 12 ,
199- borderRadius : 8 ,
200- borderWidth : 1 ,
284+ signingRow : {
285+ marginVertical : 6 ,
286+ } ,
287+ holderName : {
288+ marginBottom : 4 ,
289+ } ,
290+ signingPills : {
291+ flexDirection : "row" ,
292+ gap : 8 ,
201293 } ,
202294} ) ;
203295
0 commit comments