alpha
Login
or
Join now
cuducos.me
/
meu-garfo
Star
1
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
🍴 Meu Garfo é uma visualização em grafo dos CNPJs
cuducos.tngl.io/meu-garfo
Star
1
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
4
Pulls
Pipelines
Adapts client to the new backend API
author
Eduardo Cuducos
date
3 weeks ago
(May 31, 2026, 10:59 PM -0400)
commit
91c8e1b5
91c8e1b57eb03e1f5639a0f52e5e63aea35ac38d
parent
10cd24ce
10cd24cecaca21855eae306cdf6ba5b11015c551
+396
-336
4 changed files
Expand all
Collapse all
Unified
Split
src
Api.elm
Main.elm
Types.elm
View.elm
+14
-50
src/Api.elm
Reviewed
···
1
1
-
module Api exposing (fetchCompanyName, queryEntity, responseDecoder)
1
1
+
module Api exposing (fetchCompanyName, queryEntity)
2
2
3
3
import Http
4
4
import Json.Decode as Decode exposing (Decoder, field, string)
···
54
54
else
55
55
graphApi ++ "/"
56
56
57
57
-
( path, id ) =
57
57
+
( id, url ) =
58
58
case qType of
59
59
-
CompanyCnpj cnpj ->
60
60
-
( "qsa/", cnpj )
61
61
-
62
62
-
CompanyAsPartner cnpj ->
63
63
-
( "cnpjs/", cnpj )
59
59
+
EntityQuery term ->
60
60
+
( term, base ++ "relacoes/" ++ term )
64
61
65
65
-
PersonUid uid ->
66
66
-
( "cnpjs/", uid )
67
67
-
68
68
-
url =
69
69
-
base ++ path ++ id
62
62
+
ConnectionQuery id1 id2 ->
63
63
+
( id1 ++ ";" ++ id2, base ++ "conexao/" ++ id1 ++ "/" ++ id2 )
70
64
in
71
65
Http.get
72
66
{ url = url
···
102
96
Err (HttpError (Http.BadStatus meta.statusCode))
103
97
104
98
Http.GoodStatus_ _ body ->
105
105
-
case Decode.decodeString responseDecoder body of
99
99
+
case Decode.decodeString (Decode.list relationDecoder) body of
106
100
Ok value ->
107
101
Ok value
108
102
···
110
104
Err (HttpError (Http.BadBody (Decode.errorToString err)))
111
105
112
106
113
113
-
responseDecoder : Decoder ApiResponse
114
114
-
responseDecoder =
115
115
-
Decode.oneOf
116
116
-
[ Decode.map CompanyResponse companyDecoder
117
117
-
, Decode.map PartnerResponse partnerDecoder
118
118
-
]
119
119
-
120
120
-
121
121
-
companyDecoder : Decoder CompanyData
122
122
-
companyDecoder =
123
123
-
Decode.map3 CompanyData
124
124
-
(field "company_id" string)
125
125
-
(field "name" string)
126
126
-
(field "partners" (Decode.list partnerRefDecoder))
127
127
-
128
128
-
129
129
-
partnerRefDecoder : Decoder PartnerRef
130
130
-
partnerRefDecoder =
131
131
-
Decode.map3 PartnerRef
132
132
-
(field "partner_id" (Decode.oneOf [ string, Decode.map String.fromInt Decode.int ]))
133
133
-
(Decode.maybe (field "name" string))
134
134
-
(Decode.maybe (field "cpf" string))
135
135
-
136
136
-
137
137
-
partnerDecoder : Decoder PartnerData
138
138
-
partnerDecoder =
139
139
-
Decode.map4 PartnerData
140
140
-
(Decode.oneOf [ field "partner_id" string, field "partnerd_id" string, field "partner_id" (Decode.map String.fromInt Decode.int) ])
141
141
-
(Decode.maybe (field "name" string))
142
142
-
(Decode.maybe (field "cpf" string))
143
143
-
(field "companies" (Decode.list companyRefDecoder))
144
144
-
145
145
-
146
146
-
companyRefDecoder : Decoder CompanyRef
147
147
-
companyRefDecoder =
148
148
-
Decode.map2 CompanyRef
107
107
+
relationDecoder : Decoder Relation
108
108
+
relationDecoder =
109
109
+
Decode.map5 Relation
149
110
(field "cnpj" string)
150
150
-
(field "name" string)
111
111
+
(field "razao_social" string)
112
112
+
(field "id" string)
113
113
+
(Decode.maybe (field "nome" string))
114
114
+
(Decode.maybe (field "cpf" string))
+245
-233
src/Main.elm
Reviewed
···
41
41
let
42
42
initialModel =
43
43
{ input = ""
44
44
+
, connectionInput1 = ""
45
45
+
, connectionInput2 = ""
46
46
+
, activeTab = CnpjTab
44
47
, nodes = Dict.empty
45
48
, edges = Set.empty
46
49
, visited = Set.empty
···
64
67
65
68
( finalModel, initialCmd ) =
66
69
case parseUrl url of
67
67
-
Just cnpj ->
70
70
+
CnpjRoute cnpj ->
68
71
let
69
72
seeded =
70
73
{ initialModel | input = Format.mask cnpj }
···
72
75
in
73
76
triggerNextQuery seeded
74
77
75
75
-
Nothing ->
78
78
+
ConnectionRoute id1 id2 ->
79
79
+
let
80
80
+
seeded =
81
81
+
{ initialModel
82
82
+
| connectionInput1 = Format.mask id1
83
83
+
, connectionInput2 = Format.mask id2
84
84
+
, activeTab = ConnectionTab
85
85
+
}
86
86
+
|> enqueueQueries [ QueryRequest (id1 ++ ";" ++ id2) 0 (ConnectionQuery id1 id2) ]
87
87
+
in
88
88
+
triggerNextQuery seeded
89
89
+
90
90
+
HomeRoute ->
76
91
( initialModel, Cmd.none )
77
92
in
78
93
( finalModel
···
87
102
update msg model =
88
103
case msg of
89
104
UpdateInput s ->
90
90
-
( { model | input = Format.maskCnpjInput model.input s }, Cmd.none )
105
105
+
if String.length (String.filter Char.isAlphaNum s) > 14 then
106
106
+
( { model | input = String.left 32 s }, Cmd.none )
91
107
92
92
-
Search ->
93
93
-
let
94
94
-
unmasked =
95
95
-
String.filter Char.isAlphaNum model.input
108
108
+
else
109
109
+
( { model | input = Format.maskCnpjInput model.input s }, Cmd.none )
96
110
97
97
-
currentCnpj =
98
98
-
parseUrl model.url
99
99
-
in
100
100
-
if String.isEmpty unmasked then
101
101
-
( { model | error = Just "Por favor, digite um CNPJ" }, Cmd.none )
111
111
+
UpdateConnectionInput1 s ->
112
112
+
if String.length (String.filter Char.isAlphaNum s) > 14 then
113
113
+
( { model | connectionInput1 = String.left 32 s }, Cmd.none )
114
114
+
115
115
+
else
116
116
+
( { model | connectionInput1 = Format.maskCnpjInput model.connectionInput1 s }, Cmd.none )
102
117
103
103
-
else if currentCnpj == Just unmasked then
104
104
-
( model, Cmd.none )
118
118
+
UpdateConnectionInput2 s ->
119
119
+
if String.length (String.filter Char.isAlphaNum s) > 14 then
120
120
+
( { model | connectionInput2 = String.left 32 s }, Cmd.none )
105
121
106
122
else
107
107
-
( { model
108
108
-
| nodes = Dict.empty
109
109
-
, edges = Set.empty
110
110
-
, visited = Set.empty
111
111
-
, pending = Set.empty
112
112
-
, queryQueue = []
113
113
-
, currentQuery = Nothing
114
114
-
, error = Nothing
115
115
-
, simulation = Graph.initSimulation model.width model.height []
116
116
-
, pan = { x = 0, y = 0 }
117
117
-
, zoom = 1.0
118
118
-
}
119
119
-
, Nav.pushUrl model.navKey ("#" ++ unmasked)
120
120
-
)
123
123
+
( { model | connectionInput2 = Format.maskCnpjInput model.connectionInput2 s }, Cmd.none )
124
124
+
125
125
+
SwitchTab tab ->
126
126
+
( { model | activeTab = tab }, Cmd.none )
127
127
+
128
128
+
Search ->
129
129
+
case model.activeTab of
130
130
+
CnpjTab ->
131
131
+
let
132
132
+
unmasked =
133
133
+
String.filter Char.isAlphaNum model.input
134
134
+
135
135
+
currentRoute =
136
136
+
parseUrl model.url
137
137
+
in
138
138
+
if String.isEmpty unmasked then
139
139
+
( { model | error = Just "Por favor, digite um CNPJ" }, Cmd.none )
140
140
+
141
141
+
else if currentRoute == CnpjRoute unmasked then
142
142
+
( model, Cmd.none )
143
143
+
144
144
+
else
145
145
+
( { model
146
146
+
| nodes = Dict.empty
147
147
+
, edges = Set.empty
148
148
+
, visited = Set.empty
149
149
+
, pending = Set.empty
150
150
+
, queryQueue = []
151
151
+
, currentQuery = Nothing
152
152
+
, error = Nothing
153
153
+
, simulation = Graph.initSimulation model.width model.height []
154
154
+
, pan = { x = 0, y = 0 }
155
155
+
, zoom = 1.0
156
156
+
}
157
157
+
, Nav.pushUrl model.navKey ("#/grafo/" ++ unmasked)
158
158
+
)
159
159
+
160
160
+
ConnectionTab ->
161
161
+
let
162
162
+
id1 =
163
163
+
String.filter Char.isAlphaNum model.connectionInput1
164
164
+
165
165
+
id2 =
166
166
+
String.filter Char.isAlphaNum model.connectionInput2
167
167
+
168
168
+
currentRoute =
169
169
+
parseUrl model.url
170
170
+
in
171
171
+
if String.isEmpty id1 || String.isEmpty id2 then
172
172
+
( { model | error = Just "Por favor, digite ambos os campos" }, Cmd.none )
173
173
+
174
174
+
else if currentRoute == ConnectionRoute id1 id2 then
175
175
+
( model, Cmd.none )
176
176
+
177
177
+
else
178
178
+
( { model
179
179
+
| nodes = Dict.empty
180
180
+
, edges = Set.empty
181
181
+
, visited = Set.empty
182
182
+
, pending = Set.empty
183
183
+
, queryQueue = []
184
184
+
, currentQuery = Nothing
185
185
+
, error = Nothing
186
186
+
, simulation = Graph.initSimulation model.width model.height []
187
187
+
, pan = { x = 0, y = 0 }
188
188
+
, zoom = 1.0
189
189
+
}
190
190
+
, Nav.pushUrl model.navKey ("#/conexao/" ++ id1 ++ "/" ++ id2)
191
191
+
)
121
192
122
193
NodeClicked id ->
123
194
case Dict.get id model.nodes of
124
195
Just node ->
125
196
let
126
197
queries =
127
127
-
case node.entity of
128
128
-
Company _ cnpj ->
129
129
-
queriesFor cnpj True node.depth
130
130
-
131
131
-
Person _ _ ->
132
132
-
[ QueryRequest id node.depth (PersonUid id) ]
198
198
+
queriesFor id False node.depth
133
199
in
134
200
triggerNextQuery (enqueueQueries queries model)
135
201
···
140
206
let
141
207
key =
142
208
Maybe.map (\q -> queryKey q.queryType) model.currentQuery
143
143
-
|> Maybe.withDefault ( id, "qsa" )
209
209
+
|> Maybe.withDefault ( id, "relacoes" )
144
210
145
211
baseModel =
146
212
{ model
···
156
222
Ok response ->
157
223
let
158
224
( updatedModel, newQueue, extraCmd ) =
159
159
-
processResponse response depth { baseModel | error = Nothing }
225
225
+
processResponse id response depth { baseModel | error = Nothing }
160
226
161
227
( nextModel, nextCmd ) =
162
228
triggerNextQuery
···
267
333
268
334
UrlChanged url ->
269
335
case parseUrl url of
270
270
-
Just cnpj ->
336
336
+
CnpjRoute cnpj ->
271
337
let
272
338
isSameCnpj =
273
339
cnpj == String.filter (\c -> Char.isAlphaNum c || c == '*') model.input
···
288
354
, currentQuery = Nothing
289
355
, simulation = Graph.initSimulation model.width model.height []
290
356
, url = url
357
357
+
, activeTab = CnpjTab
291
358
}
292
359
in
293
360
triggerNextQuery (enqueueQueries (queriesFor cnpj True 0) reset)
294
361
295
295
-
Nothing ->
362
362
+
ConnectionRoute id1 id2 ->
363
363
+
let
364
364
+
isSame =
365
365
+
id1 == String.filter Char.isAlphaNum model.connectionInput1 && id2 == String.filter Char.isAlphaNum model.connectionInput2
366
366
+
in
367
367
+
if isSame && not (Dict.isEmpty model.nodes) then
368
368
+
( model, Cmd.none )
369
369
+
370
370
+
else
371
371
+
let
372
372
+
reset =
373
373
+
{ model
374
374
+
| connectionInput1 = Format.mask id1
375
375
+
, connectionInput2 = Format.mask id2
376
376
+
, nodes = Dict.empty
377
377
+
, edges = Set.empty
378
378
+
, visited = Set.empty
379
379
+
, pending = Set.empty
380
380
+
, queryQueue = []
381
381
+
, currentQuery = Nothing
382
382
+
, simulation = Graph.initSimulation model.width model.height []
383
383
+
, url = url
384
384
+
, activeTab = ConnectionTab
385
385
+
}
386
386
+
in
387
387
+
triggerNextQuery (enqueueQueries [ QueryRequest (id1 ++ ";" ++ id2) 0 (ConnectionQuery id1 id2) ] reset)
388
388
+
389
389
+
HomeRoute ->
296
390
( { model | url = url }, Cmd.none )
297
391
298
392
299
299
-
parseUrl : Url -> Maybe String
393
393
+
type Route
394
394
+
= CnpjRoute String
395
395
+
| ConnectionRoute String String
396
396
+
| HomeRoute
397
397
+
398
398
+
399
399
+
parseUrl : Url -> Route
300
400
parseUrl url =
301
401
case url.fragment of
302
402
Just frag ->
303
403
let
304
304
-
cnpj =
305
305
-
String.split ";" frag
306
306
-
|> List.head
307
307
-
|> Maybe.withDefault ""
404
404
+
cleanFrag =
405
405
+
if String.startsWith "/" frag then
406
406
+
String.dropLeft 1 frag
407
407
+
408
408
+
else
409
409
+
frag
410
410
+
411
411
+
parts =
412
412
+
String.split "/" cleanFrag
308
413
in
309
309
-
if String.isEmpty cnpj then
310
310
-
Nothing
414
414
+
case parts of
415
415
+
[ "conexao", id1, id2 ] ->
416
416
+
ConnectionRoute id1 id2
417
417
+
418
418
+
[ "grafo", id ] ->
419
419
+
CnpjRoute id
420
420
+
421
421
+
_ ->
422
422
+
let
423
423
+
semiParts =
424
424
+
String.split ";" frag
425
425
+
in
426
426
+
case semiParts of
427
427
+
[ id1, id2 ] ->
428
428
+
ConnectionRoute id1 id2
429
429
+
430
430
+
[ id ] ->
431
431
+
if String.isEmpty id then
432
432
+
HomeRoute
311
433
312
312
-
else
313
313
-
Just cnpj
434
434
+
else
435
435
+
CnpjRoute id
436
436
+
437
437
+
_ ->
438
438
+
HomeRoute
314
439
315
440
Nothing ->
316
316
-
Nothing
441
441
+
HomeRoute
317
442
318
443
319
444
triggerNextQuery : Model -> ( Model, Cmd Msg )
···
337
462
( model, Cmd.none )
338
463
339
464
340
340
-
processResponse : ApiResponse -> Int -> Model -> ( Model, List QueryRequest, Cmd Msg )
341
341
-
processResponse response depth model =
342
342
-
case response of
343
343
-
CompanyResponse data ->
465
465
+
processResponse : String -> ApiResponse -> Int -> Model -> ( Model, List QueryRequest, Cmd Msg )
466
466
+
processResponse queriedId relations depth model =
467
467
+
let
468
468
+
processRelation rel ( accModel, accCmds ) =
344
469
let
345
345
-
( nx, ny ) =
346
346
-
nodeOffset data.id model.width model.height
470
470
+
( c_nx, c_ny ) =
471
471
+
if Dict.member rel.cnpj accModel.nodes then
472
472
+
( 0, 0 )
473
473
+
474
474
+
else
475
475
+
spawnNear rel.partnerId rel.cnpj accModel
347
476
348
348
-
newNode =
349
349
-
case Dict.get data.id model.nodes of
477
477
+
companyNode =
478
478
+
case Dict.get rel.cnpj accModel.nodes of
350
479
Just existing ->
351
351
-
{ existing | entity = Company data.name data.id }
480
480
+
{ existing | entity = Company rel.razaoSocial rel.cnpj }
352
481
353
482
Nothing ->
354
354
-
{ id = data.id, entity = Company data.name data.id, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing }
483
483
+
{ id = rel.cnpj, entity = Company rel.razaoSocial rel.cnpj, depth = depth + 1, x = c_nx, y = c_ny, vx = 0, vy = 0, error = Nothing }
355
484
356
356
-
updatedNodes =
357
357
-
Dict.insert data.id newNode model.nodes
358
358
-
359
359
-
( modelWithPartners, _, cmd ) =
360
360
-
List.foldl (processPartnerEdge data.id depth) ( { model | nodes = updatedNodes }, [], Cmd.none ) data.partners
361
361
-
in
362
362
-
( modelWithPartners
363
363
-
, enqueueChildren data.id depth modelWithPartners
364
364
-
, cmd
365
365
-
)
485
485
+
( p_nx, p_ny ) =
486
486
+
if Dict.member rel.partnerId accModel.nodes then
487
487
+
( 0, 0 )
366
488
367
367
-
PartnerResponse data ->
368
368
-
let
369
369
-
isCompany =
370
370
-
isCnpj data.id
489
489
+
else
490
490
+
spawnNear rel.cnpj rel.partnerId accModel
371
491
372
372
-
( nx, ny ) =
373
373
-
nodeOffset data.id model.width model.height
492
492
+
partnerName =
493
493
+
Maybe.withDefault rel.partnerId rel.partnerName
374
494
375
375
-
newNode =
376
376
-
case Dict.get data.id model.nodes of
495
495
+
partnerNode =
496
496
+
case Dict.get rel.partnerId accModel.nodes of
377
497
Just existing ->
378
378
-
if isCompany then
379
379
-
existing
380
380
-
381
381
-
else
382
382
-
let
383
383
-
name =
384
384
-
Maybe.withDefault data.id data.name
385
385
-
in
386
386
-
case existing.entity of
387
387
-
Person oldName oldCpf ->
388
388
-
{ existing
389
389
-
| entity =
390
390
-
Person name
391
391
-
(if data.cpf /= Nothing then
392
392
-
data.cpf
498
498
+
case existing.entity of
499
499
+
Person _ _ ->
500
500
+
{ existing | entity = Person partnerName rel.partnerCpf }
393
501
394
394
-
else
395
395
-
oldCpf
396
396
-
)
397
397
-
}
398
398
-
399
399
-
_ ->
400
400
-
{ existing | entity = Person name data.cpf }
502
502
+
Company _ _ ->
503
503
+
existing
401
504
402
505
Nothing ->
403
403
-
if isCompany then
404
404
-
{ id = data.id, entity = Company (Maybe.withDefault data.id data.name) data.id, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing }
506
506
+
let
507
507
+
entity =
508
508
+
if isCnpj rel.partnerId then
509
509
+
Company partnerName rel.partnerId
405
510
406
406
-
else
407
407
-
let
408
408
-
name =
409
409
-
Maybe.withDefault data.id data.name
410
410
-
in
411
411
-
{ id = data.id, entity = Person name data.cpf, depth = depth, x = nx, y = ny, vx = 0, vy = 0, error = Nothing }
511
511
+
else
512
512
+
Person partnerName rel.partnerCpf
513
513
+
in
514
514
+
{ id = rel.partnerId, entity = entity, depth = depth + 1, x = p_nx, y = p_ny, vx = 0, vy = 0, error = Nothing }
515
515
+
516
516
+
edge =
517
517
+
( rel.cnpj, rel.partnerId )
412
518
413
519
updatedNodes =
414
414
-
Dict.insert data.id newNode model.nodes
520
520
+
accModel.nodes
521
521
+
|> Dict.insert rel.cnpj companyNode
522
522
+
|> Dict.insert rel.partnerId partnerNode
415
523
416
416
-
( modelWithCompanies, _, cmd ) =
417
417
-
List.foldl (processCompanyEdge data.id depth) ( { model | nodes = updatedNodes }, [], Cmd.none ) data.companies
524
524
+
updatedEdges =
525
525
+
Set.insert edge accModel.edges
418
526
in
419
419
-
( modelWithCompanies
420
420
-
, enqueueChildren data.id depth modelWithCompanies
421
421
-
, cmd
422
422
-
)
423
423
-
424
424
-
425
425
-
processPartnerEdge : String -> Int -> PartnerRef -> ( Model, List QueryRequest, Cmd Msg ) -> ( Model, List QueryRequest, Cmd Msg )
426
426
-
processPartnerEdge companyId depth partner ( model, _, cmds ) =
427
427
-
let
428
428
-
edge =
429
429
-
( companyId, partner.id )
430
430
-
431
431
-
updatedEdges =
432
432
-
Set.insert edge model.edges
433
433
-
434
434
-
name =
435
435
-
Maybe.withDefault partner.id partner.name
436
436
-
437
437
-
isPartnerCnpj =
438
438
-
isCnpj partner.id
527
527
+
( { accModel | nodes = updatedNodes, edges = updatedEdges }, accCmds )
439
528
440
440
-
entity =
441
441
-
if isPartnerCnpj then
442
442
-
Company name partner.id
529
529
+
( modelWithNodes, cmds ) =
530
530
+
List.foldl processRelation ( model, Cmd.none ) relations
443
531
444
444
-
else
445
445
-
Person name partner.cpf
446
446
-
447
447
-
( nx, ny ) =
448
448
-
spawnNear companyId partner.id model
449
449
-
450
450
-
targetNode =
451
451
-
case Dict.get partner.id model.nodes of
452
452
-
Just existing ->
453
453
-
if partner.name /= Nothing || partner.cpf /= Nothing then
454
454
-
case existing.entity of
455
455
-
Person oldName oldCpf ->
456
456
-
{ existing
457
457
-
| entity =
458
458
-
Person
459
459
-
(if partner.name /= Nothing then
460
460
-
name
461
461
-
462
462
-
else
463
463
-
oldName
464
464
-
)
465
465
-
(if partner.cpf /= Nothing then
466
466
-
partner.cpf
467
467
-
468
468
-
else
469
469
-
oldCpf
470
470
-
)
471
471
-
}
472
472
-
473
473
-
_ ->
474
474
-
{ existing | entity = entity }
532
532
+
finalNodes =
533
533
+
let
534
534
+
idsToUpdate =
535
535
+
if String.contains ";" queriedId then
536
536
+
String.split ";" queriedId
475
537
476
538
else
477
477
-
existing
478
478
-
479
479
-
Nothing ->
480
480
-
{ id = partner.id, entity = entity, depth = depth + 1, x = nx, y = ny, vx = 0, vy = 0, error = Nothing }
481
481
-
482
482
-
newModel =
483
483
-
{ model | edges = updatedEdges, nodes = Dict.insert partner.id targetNode model.nodes }
484
484
-
485
485
-
newCmd =
486
486
-
if isPartnerCnpj && partner.name == Nothing then
487
487
-
Cmd.batch [ cmds, Api.fetchCompanyName model.jsonApi partner.id ]
488
488
-
489
489
-
else
490
490
-
cmds
491
491
-
in
492
492
-
( newModel, [], newCmd )
493
493
-
494
494
-
495
495
-
processCompanyEdge : String -> Int -> CompanyRef -> ( Model, List QueryRequest, Cmd Msg ) -> ( Model, List QueryRequest, Cmd Msg )
496
496
-
processCompanyEdge partnerId depth company ( model, _, cmds ) =
497
497
-
let
498
498
-
edge =
499
499
-
( company.id, partnerId )
539
539
+
[ queriedId ]
500
540
501
501
-
updatedEdges =
502
502
-
Set.insert edge model.edges
503
503
-
504
504
-
( nx, ny ) =
505
505
-
spawnNear partnerId company.id model
541
541
+
updateDepth id dict =
542
542
+
Dict.update id
543
543
+
(Maybe.map (\n -> if n.depth > depth then { n | depth = depth } else n))
544
544
+
dict
545
545
+
in
546
546
+
List.foldl updateDepth modelWithNodes.nodes idsToUpdate
547
547
+
in
548
548
+
( { modelWithNodes | nodes = finalNodes }
506
549
507
507
-
targetNode =
508
508
-
case Dict.get company.id model.nodes of
509
509
-
Just existing ->
510
510
-
{ existing | entity = Company company.name company.id }
511
511
-
512
512
-
Nothing ->
513
513
-
{ id = company.id, entity = Company company.name company.id, depth = depth + 1, x = nx, y = ny, vx = 0, vy = 0, error = Nothing }
514
514
-
515
515
-
newModel =
516
516
-
{ model | edges = updatedEdges, nodes = Dict.insert company.id targetNode model.nodes }
517
517
-
in
518
518
-
( newModel, [], cmds )
550
550
+
, enqueueChildren queriedId depth modelWithNodes
551
551
+
, cmds
552
552
+
)
519
553
520
554
521
555
isCnpj : String -> Bool
···
536
570
queryKey : QueryType -> QueryKey
537
571
queryKey qType =
538
572
case qType of
539
539
-
CompanyCnpj cnpj ->
540
540
-
( cnpj, "qsa" )
573
573
+
EntityQuery id ->
574
574
+
( id, "relacoes" )
541
575
542
542
-
CompanyAsPartner cnpj ->
543
543
-
( cnpj, "cnpjs" )
544
544
-
545
545
-
PersonUid uid ->
546
546
-
( uid, "cnpjs" )
576
576
+
ConnectionQuery id1 id2 ->
577
577
+
( id1 ++ ";" ++ id2, "conexao" )
547
578
548
579
549
580
queriesFor : String -> Bool -> Int -> List QueryRequest
550
550
-
queriesFor id isCompany depth =
551
551
-
if isCompany then
552
552
-
[ QueryRequest id depth (CompanyCnpj id)
553
553
-
, QueryRequest id depth (CompanyAsPartner id)
554
554
-
]
555
555
-
556
556
-
else
557
557
-
[ QueryRequest id depth (PersonUid id) ]
581
581
+
queriesFor id _ depth =
582
582
+
[ QueryRequest id depth (EntityQuery id) ]
558
583
559
584
560
585
enqueueQueries : List QueryRequest -> Model -> Model
···
698
723
699
724
700
725
handleApiError : String -> String -> Maybe QueryRequest -> ApiError -> Model -> Model
701
701
-
handleApiError id url currentQuery apiErr model =
702
702
-
let
703
703
-
wasPartnerLookup =
704
704
-
case currentQuery |> Maybe.map .queryType of
705
705
-
Just (CompanyAsPartner _) ->
706
706
-
True
707
707
-
708
708
-
_ ->
709
709
-
False
710
710
-
in
726
726
+
handleApiError id url _ apiErr model =
711
727
case apiErr of
712
728
NotFound ->
713
713
-
if wasPartnerLookup then
714
714
-
model
715
715
-
716
716
-
else
717
717
-
markNodeError id (Just "Não encontrado") model
729
729
+
markNodeError id (Just "Não encontrado") model
718
730
719
731
BadRequest msg ->
720
732
let
+21
-31
src/Types.elm
Reviewed
···
9
9
import Url exposing (Url)
10
10
11
11
12
12
+
type Tab
13
13
+
= CnpjTab
14
14
+
| ConnectionTab
15
15
+
16
16
+
12
17
type Entity
13
18
= Company String String -- Nome, CNPJ
14
19
| Person String (Maybe String) -- Nome, CPF
···
33
38
34
39
35
40
type QueryType
36
36
-
= CompanyCnpj String
37
37
-
| CompanyAsPartner String
38
38
-
| PersonUid String
41
41
+
= EntityQuery String
42
42
+
| ConnectionQuery String String
39
43
40
44
41
45
type alias QueryRequest =
···
51
55
52
56
type alias Model =
53
57
{ input : String
58
58
+
, connectionInput1 : String
59
59
+
, connectionInput2 : String
60
60
+
, activeTab : Tab
54
61
, nodes : Dict String Node
55
62
, edges : Set ( String, String )
56
63
, visited : Set QueryKey
···
75
82
76
83
type Msg
77
84
= UpdateInput String
85
85
+
| UpdateConnectionInput1 String
86
86
+
| UpdateConnectionInput2 String
87
87
+
| SwitchTab Tab
78
88
| Search
79
89
| NodeClicked String
80
90
| GotResponse String String Int (Result ApiError ApiResponse)
···
90
100
| UrlChanged Url
91
101
92
102
93
93
-
type ApiResponse
94
94
-
= CompanyResponse CompanyData
95
95
-
| PartnerResponse PartnerData
96
96
-
97
97
-
98
98
-
type alias CompanyData =
99
99
-
{ id : String
100
100
-
, name : String
101
101
-
, partners : List PartnerRef
102
102
-
}
103
103
-
104
104
-
105
105
-
type alias PartnerRef =
106
106
-
{ id : String
107
107
-
, name : Maybe String
108
108
-
, cpf : Maybe String
109
109
-
}
110
110
-
111
111
-
112
112
-
type alias PartnerData =
113
113
-
{ id : String
114
114
-
, name : Maybe String
115
115
-
, cpf : Maybe String
116
116
-
, companies : List CompanyRef
117
117
-
}
103
103
+
type alias ApiResponse =
104
104
+
List Relation
118
105
119
106
120
120
-
type alias CompanyRef =
121
121
-
{ id : String
122
122
-
, name : String
107
107
+
type alias Relation =
108
108
+
{ cnpj : String
109
109
+
, razaoSocial : String
110
110
+
, partnerId : String
111
111
+
, partnerName : Maybe String
112
112
+
, partnerCpf : Maybe String
123
113
}
+116
-22
src/View.elm
Reviewed
···
50
50
[ nav []
51
51
[ ul []
52
52
[ li [] [ h1 [] [ text "Meu Garfo" ] ]
53
53
-
]
54
54
-
, ul [ class "controls" ]
55
55
-
[ li []
56
56
-
[ label [ for "cnpj-input" ] [ text "CNPJ" ]
57
57
-
, input
58
58
-
[ id "cnpj-input"
59
59
-
, class "cnpj-input"
60
60
-
, placeholder "00.000.000/0000-00"
61
61
-
, value model.input
62
62
-
, onInput UpdateInput
63
63
-
, onEnter Search
64
64
-
, attribute "aria-label" "CNPJ"
53
53
+
, li []
54
54
+
[ a
55
55
+
[ href "#"
56
56
+
, preventDefaultOnClick (SwitchTab CnpjTab)
57
57
+
, class
58
58
+
(if model.activeTab == CnpjTab then
59
59
+
""
60
60
+
61
61
+
else
62
62
+
"secondary"
63
63
+
)
64
64
+
, attribute "style"
65
65
+
(if model.activeTab == CnpjTab then
66
66
+
"font-weight: bold; border-bottom: 2px solid var(--pico-primary); border-radius: 0;"
67
67
+
68
68
+
else
69
69
+
""
70
70
+
)
65
71
]
66
66
-
[]
72
72
+
[ text "CNPJ" ]
67
73
]
68
74
, li []
69
69
-
[ button
70
70
-
[ onClick Search
71
71
-
, Html.Attributes.disabled (model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue))
72
72
-
, class "primary"
73
73
-
]
74
74
-
[ text
75
75
-
(if model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue) then
76
76
-
"Carregando..."
75
75
+
[ a
76
76
+
[ href "#"
77
77
+
, preventDefaultOnClick (SwitchTab ConnectionTab)
78
78
+
, class
79
79
+
(if model.activeTab == ConnectionTab then
80
80
+
""
77
81
78
82
else
79
79
-
"Buscar"
83
83
+
"secondary"
84
84
+
)
85
85
+
, attribute "style"
86
86
+
(if model.activeTab == ConnectionTab then
87
87
+
"font-weight: bold; border-bottom: 2px solid var(--pico-primary); border-radius: 0;"
88
88
+
89
89
+
else
90
90
+
""
80
91
)
81
92
]
93
93
+
[ text "Conexões" ]
82
94
]
83
95
]
96
96
+
, ul [ class "controls" ]
97
97
+
(case model.activeTab of
98
98
+
CnpjTab ->
99
99
+
[ li []
100
100
+
[ label [ for "cnpj-input" ] [ text "CNPJ" ]
101
101
+
, input
102
102
+
[ id "cnpj-input"
103
103
+
, class "cnpj-input"
104
104
+
, placeholder "00.000.000/0000-00"
105
105
+
, value model.input
106
106
+
, onInput UpdateInput
107
107
+
, onEnter Search
108
108
+
, attribute "aria-label" "CNPJ"
109
109
+
]
110
110
+
[]
111
111
+
]
112
112
+
, li []
113
113
+
[ button
114
114
+
[ onClick Search
115
115
+
, Html.Attributes.disabled (model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue))
116
116
+
, class "primary"
117
117
+
]
118
118
+
[ text
119
119
+
(if model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue) then
120
120
+
"Carregando..."
121
121
+
122
122
+
else
123
123
+
"Buscar"
124
124
+
)
125
125
+
]
126
126
+
]
127
127
+
]
128
128
+
129
129
+
ConnectionTab ->
130
130
+
[ li []
131
131
+
[ label [ for "connection-input-1" ] [ text "Origem" ]
132
132
+
, input
133
133
+
[ id "connection-input-1"
134
134
+
, class "cnpj-input"
135
135
+
, placeholder "CNPJ ou ID"
136
136
+
, value model.connectionInput1
137
137
+
, onInput UpdateConnectionInput1
138
138
+
, onEnter Search
139
139
+
, attribute "aria-label" "Origem"
140
140
+
]
141
141
+
[]
142
142
+
]
143
143
+
, li []
144
144
+
[ label [ for "connection-input-2" ] [ text "Destino" ]
145
145
+
, input
146
146
+
[ id "connection-input-2"
147
147
+
, class "cnpj-input"
148
148
+
, placeholder "CNPJ ou ID"
149
149
+
, value model.connectionInput2
150
150
+
, onInput UpdateConnectionInput2
151
151
+
, onEnter Search
152
152
+
, attribute "aria-label" "Destino"
153
153
+
]
154
154
+
[]
155
155
+
]
156
156
+
, li []
157
157
+
[ button
158
158
+
[ onClick Search
159
159
+
, Html.Attributes.disabled (model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue))
160
160
+
, class "primary"
161
161
+
]
162
162
+
[ text
163
163
+
(if model.currentQuery /= Nothing || not (List.isEmpty model.queryQueue) then
164
164
+
"Carregando..."
165
165
+
166
166
+
else
167
167
+
"Buscar"
168
168
+
)
169
169
+
]
170
170
+
]
171
171
+
]
172
172
+
)
84
173
]
85
174
]
86
175
, main_ [ class "graph-viewport", onWheel Zoom ]
···
127
216
onWheel : (Float -> Msg) -> Html.Attribute Msg
128
217
onWheel tagger =
129
218
preventDefaultOn "wheel" (Decode.map (\delta -> ( tagger delta, True )) (Decode.field "deltaY" Decode.float))
219
219
+
220
220
+
221
221
+
preventDefaultOnClick : msg -> Html.Attribute msg
222
222
+
preventDefaultOnClick msg =
223
223
+
preventDefaultOn "click" (Decode.succeed ( msg, True ))
130
224
131
225
132
226
onEnter : Msg -> Html.Attribute Msg