From ab87a6abfd8f346944b0b883034ddf980dce5c80 Mon Sep 17 00:00:00 2001 From: rubenponce9161-max Date: Thu, 23 Jul 2026 20:02:58 -0400 Subject: [PATCH] test-complete --- frontend_focused/README.md | 76 ++++ frontend_focused/backend/db.sqlite3 | Bin 0 -> 225280 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 166 bytes .../__pycache__/settings.cpython-313.pyc | Bin 0 -> 3299 bytes .../server/__pycache__/urls.cpython-313.pyc | Bin 0 -> 1466 bytes .../server/__pycache__/wsgi.cpython-313.pyc | Bin 0 -> 652 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 239 bytes .../__pycache__/admin.cpython-313.pyc | Bin 0 -> 3781 bytes .../__pycache__/apps.cpython-313.pyc | Bin 0 -> 588 bytes .../__pycache__/models.cpython-313.pyc | Bin 0 -> 7645 bytes .../__pycache__/serializers.cpython-313.pyc | Bin 0 -> 5527 bytes .../__pycache__/tests.cpython-313.pyc | Bin 0 -> 13111 bytes .../__pycache__/views.cpython-313.pyc | Bin 0 -> 3140 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 237 bytes .../__pycache__/submission.cpython-313.pyc | Bin 0 -> 3107 bytes .../backend/submissions/filters/submission.py | 44 +- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 234 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 238 bytes .../seed_submissions.cpython-313.pyc | Bin 0 -> 7417 bytes .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 5261 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 182 bytes frontend_focused/backend/submissions/tests.py | 174 ++++++++ frontend_focused/backend/submissions/views.py | 7 +- .../frontend/app/submissions/[id]/page.tsx | 195 ++++++++- .../frontend/app/submissions/page.tsx | 390 ++++++++++++++++-- .../frontend/components/QueryFeedback.tsx | 58 +++ .../frontend/components/StatusChip.tsx | 45 ++ frontend_focused/frontend/lib/format.ts | 41 ++ .../frontend/lib/hooks/useBrokerOptions.ts | 2 +- .../frontend/lib/hooks/useSubmissions.ts | 9 +- frontend_focused/frontend/lib/types.ts | 5 + 31 files changed, 990 insertions(+), 56 deletions(-) create mode 100644 frontend_focused/backend/db.sqlite3 create mode 100644 frontend_focused/backend/server/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/server/__pycache__/settings.cpython-313.pyc create mode 100644 frontend_focused/backend/server/__pycache__/urls.cpython-313.pyc create mode 100644 frontend_focused/backend/server/__pycache__/wsgi.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/admin.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/apps.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/models.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/serializers.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/tests.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/__pycache__/views.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/filters/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/filters/__pycache__/submission.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/management/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/management/commands/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/management/commands/__pycache__/seed_submissions.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 frontend_focused/backend/submissions/tests.py create mode 100644 frontend_focused/frontend/components/QueryFeedback.tsx create mode 100644 frontend_focused/frontend/components/StatusChip.tsx create mode 100644 frontend_focused/frontend/lib/format.ts diff --git a/frontend_focused/README.md b/frontend_focused/README.md index 020ff695..1162326f 100644 --- a/frontend_focused/README.md +++ b/frontend_focused/README.md @@ -132,3 +132,79 @@ Visit `http://localhost:3000/submissions` to start building. ## Optional Bonus Authentication, deployment, or extra tooling are not required but welcome if scope allows. + +--- + +## Solution Summary + +### Approach + +**Backend** — `SubmissionFilterSet` (`backend/submissions/filters/submission.py`) was extended beyond +the starter `status` filter to support `brokerId` (exact match on FK id), `companySearch` (case +insensitive match across company legal name, industry, and headquarters city via `Q` objects), and the +optional extras `createdFrom`/`createdTo` (date range on `created_at`, with `createdTo` treated as +inclusive of the whole day when only a date is supplied) and `hasDocuments`/`hasNotes` (boolean filters +against the annotated `document_count`/`note_count` used for the list view). `SubmissionViewSet.get_queryset` +adds `select_related` for `broker`/`company`/`owner` on both list and detail actions and +`prefetch_related` for `contacts`/`documents`/`notes` on detail, to avoid N+1 queries. The `BrokerViewSet` +disables pagination so the frontend dropdown can consume a flat array. + +**Frontend** — All three React Query hooks (`useSubmissionsList`, `useSubmissionDetail`, +`useBrokerOptions`) were enabled and wired to real endpoints. The `/submissions` list page keeps filter +state in React state, debounces the company search input (400ms), and syncs every filter plus the current +page to the URL query string via `useSearchParams`/`router.replace`, so views are shareable/bookmarkable +and survive a refresh. Results render in an MUI table with status/priority chips, document/note counts, +a latest-note preview, and page-based pagination driven by the API's `count`/page-size. A collapsible +"More filters" section exposes the optional `createdFrom`/`createdTo` date range and `hasDocuments`/ +`hasNotes` tri-state selects. Loading, error (with retry), and empty states are handled by a shared +`QueryFeedback` component reused on both the list and detail pages. The `/submissions/[id]` detail page +renders the summary, a contacts table, a documents list (linking to `fileUrl`), and a notes timeline. + +### Tradeoffs & Assumptions + +- **Company search** matches legal name, industry, *and* city in one field rather than three separate + inputs, favoring a simpler UX over precision; a real product might offer per-field search or a + typeahead. +- **Date filters** use native HTML `` fields instead of pulling in + `@mui/x-date-pickers` as a new dependency, keeping the bundle smaller at a small cost to visual + polish. +- **Pagination** is page-number based (matching DRF's default `PageNumberPagination`) rather than + cursor-based, which is simpler but can shift results slightly if records are created between page + loads. +- **Broker pagination was disabled** entirely rather than teaching the frontend to page through broker + results, since the dataset is small and a dropdown needs the full list anyway. +- **No authentication** was added (per the optional bonus section) since the challenge scope is a + single internal workspace view. + +### Stretch Goals Implemented + +- Optional filters `createdFrom`, `createdTo`, `hasDocuments`, `hasNotes` (backend + frontend UI). +- Filter state fully synced to the URL (not just wired to the query function). +- Debounced company search to avoid firing a request per keystroke. +- Query-level optimizations (`select_related`/`prefetch_related`) to avoid N+1s on list/detail. +- Targeted backend tests (`backend/submissions/tests.py`) covering every filter, the detail payload + shape, 404 handling, and the unpaginated brokers endpoint. +- Shared, reusable UI primitives (`StatusChip`, `PriorityChip`, `QueryFeedback`) instead of one-off + markup per page. + +### How to Run + +See [Getting Started](#getting-started) above. In short: + +```bash +# Backend +cd backend +python -m venv .venv && .venv\Scripts\activate # or source .venv/bin/activate on macOS/Linux +pip install -r requirements.txt +python manage.py migrate +python manage.py seed_submissions --force +python manage.py test submissions # optional: run the test suite +python manage.py runserver 0.0.0.0:8000 + +# Frontend (separate terminal) +cd frontend +npm install +npm run dev # if Turbopack crashes on your machine, use: npx next dev --webpack +``` + +Then visit `http://localhost:3000/submissions`. diff --git a/frontend_focused/backend/db.sqlite3 b/frontend_focused/backend/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..eee32c8c742758b23e6d2c4baab45bc424c241bf GIT binary patch literal 225280 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 literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/server/__pycache__/__init__.cpython-313.pyc b/frontend_focused/backend/server/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..356337d0382b7c608d8cb42162892634b6bb837f GIT binary patch literal 166 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 literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/__pycache__/__init__.cpython-313.pyc b/frontend_focused/backend/submissions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc16789f3d0cc8787e7430ec332cec03ff1f9d29 GIT binary patch literal 239 zcmey&%ge<81UovDvh0EMV-N=h7@>^M96-iYhG2#whIB?vrYh6m(xlwX;^NHwykdpK zf&zuK{33;tj8p}LxI#!#VsdtBkwS7tVopwKUV5sYpC;oi_W1ae{N(ufl?5>t$@JSILqGY_aM oUaz3?7Kcr4eoARhs$CHq&}fj`i$U)Gz|6?Vc#FZhhy}<20E2l)qyPW_ literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/__pycache__/admin.cpython-313.pyc b/frontend_focused/backend/submissions/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64350e15460df769530de74b871c481c5789be25 GIT binary patch literal 3781 zcmb_fTW=Fr5I*bmExzVrL#QB_#9S64=!LX`B50v0Eg?a|@MdYXwl~hgdN-Y2tMyyd zNL1-dlLyrPgz^viGiv3*(UB@us`|j2E9VDv=IlDQsgt+}OPSqs&NnmP%$YN1(@7>{ z41d3Em7dLq8T*R{-5-v{uLCk;FIj;Vq$T!10x8D|fr7jgcp!tEV@2t4)JOI`W?4~w zENu5p;_HIsji=cxD+J$Tg%Ii|1~D{RK%e62Js3oe@H6g%`W^#*!sv(eNAWXugr7{G z1OIivPZa%-{wRLpA_Fnhh@-~z5X*iBASsXuL?(|xrUWvD$n-HtT0d;paznWE@7g%p zpFsUlq9oj5e)7lonM6NRBl;oSVSX+h<0pfDvLpH-++lv^kMVN?{hS=p&&leva_Ti1 zR3skKDmBA;O?!Z&aLumhrc>_s=5G({oW|LgtjItrFbEVRkP86_7Gww&f)K8TlrWFq z1^c-Up9`CG#+3jMT3Su#*>x~#8Z^|hZMj<6RrQ)?n2H2rhHF(m|H7KtkMmH-C&#J29YOYaM zHP;2BRCjepRUwWpJNU=={l%NbvRyH(YSA=mhO3s>G}F|rs$N_LB&u5#b=5A{9lcU4 zY31iQE;{v6&2SvUww$6!-qrO6M9`WL6tqLt`-1&Bb$#REUgpf^GSJ}`%Ws|8#67#V zu2~H)wd6pNdCb(SnkllzqlQ(fJ1#W%#G0;EFlXTE;HYK8ZD5*D5d#W*1xQ)EdbF}M zJlPerc)##Za5A3;cz#1SOV z5J?iGdq7ell9#(AX?Zxw_i<&ge2c+hw+Mu>tRqM~Du^fVc?J8mMK$-DnkfWGh-4~b zkfcG1M4ANM7Z5_hBfcgNiBrWx`gbm9Fhu}T?o0nH4M~uBhfKU+dx^=PR<>3)7WWd# z%|#DPkarqg>IX>73- z+tvNJM1n_)D@ba`#57Maks!N$F)eK^4a7u{ftX~RpkL=Qiot5^Z>GgaFcxq1bg zp%R1zRELj9Tqp4{S(EsbZ%tKut;MhO#L!BgKs_E8O?cHXF>yeLQ`EB9bde<{uV1hkXTHRf0rz+1ht7>1R zuL`{2>Awla{r(#Ty^0?PbQZkN5*}No&l^EZ+{V}wO+FopBE;ch4 z_i-)d%{*;oFEz85`ew4NiF|V+FJ^{&2}(D6UU1-h!MAqmzcEPChrJkZ;KzV(?Pw33 z-bwFDx&447eJK&md^3~pFd7m5Tw5&kE5vu=yV7Y=xU(HRCv?tlmxbZK(s9`G%SKW-_zb+S))tzMU0E--G=XolEJjz~X)Y z=ZGMJU!zwT@C5Cmdx-RIBa-C!QPSl>uTHc6MRYRk*Y}#{xzUxZ%P!n$ymA(k0M1d4 z022f~f*~P2NJ#>HlEQ%WVXYoyYkr%w#Y|c&jkX=#MH(tk?(id~Ycu7=bY{3vcDgU? zz1EonDHQi3dsTKazT%j?1y;Tn@D&|qLqDWcvzegOZ%`U&S{3morN=E(tH?DCO3Q_w>bl=fS5puzi_ym?eI+T3%#;c)gAzj@Bhc5hV84{ki1jo^2J UG5(3}{uw0r;g|KlD41o{Kl1XW7ytkO literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/__pycache__/models.cpython-313.pyc b/frontend_focused/backend/submissions/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80378daaf2b915fd61ab7e3920888fedfda42ddb GIT binary patch literal 7645 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 literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/__pycache__/views.cpython-313.pyc b/frontend_focused/backend/submissions/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..658584ff08dc912e42a4c9a03d6e5c8101ef968e GIT binary patch literal 3140 zcmcImO>Emn79Q%)v}B1&>_jTFn<%yGRJNma5IeuFw@tI#)NY)XjDiAEmY}4uLx(2a zA(hkt0{FB@4vTbw21rjW&`WyDF~h`)_N@|41P8GwTFjyd!LX3Br9OBbl2=a~$F3A|x^wCD8)Y zMdo93aT53B=zL;VjUnT>dWgl4b zudz%o^%s-R*tBAl#@{!ahE4fPO&b#lFH&A^);?=uvR33Me?!AJEo@uG2u-Y*4Xj%< zWnrSJx^@c_3rq&Vd_}V?%{2b1>EJ`gc0CLap-88*eG7!YK{l$0a55qhnIq9^O!BhW zI{_hS;N?E+Ny4kT>5hu<)qavJqbW{~h-jH7DLE=4G9brDx{Ti8NM-~T<8)6L>J``* z1p15z4|T4`a|&dZ(d{b`_bhiGRl0+hqh)jzPBvK8;L1pfb%(ox%$@lZA@$z~RiG~X zPht!0_;%fSdWR8oE8U&Tk?BsXa`ZGO?RnK!js+G*q|8Z!yI92KgxnWYjkNDoi%Krg z$-LYzCr?LuG^vi}jliZHkq!iM_?HKQIH1ncF%WMUjp>?Y)5z*|Kv3H1B1q91w^B#z z1gkb~D5^~pHDX@Jg!a|Vl~vVPqjB@50sa1lS#Pdj!?rFnm^Q$hX4GvOH%uD?M%AW$ z%_NGUuHa{^WN55rHr8k|bPagO1WuH?#{o$;xg+n@Aq2}-JeBW_W_xv>zx}LP*tVWXie z!hu-0QLU-<>(H-;hiX+l7A>u=kzp3v!B=Rg)SD5;ebz7>+HdP( zt!jEWR^-T0m~1h$7RWK?XNZ+Wc-#7QNk>B;om;U-Ab)0R?-ac1%=%+t<^M96-iYhG2#whIB?vrYdc>%$$8- z$;?ZS$;r&kEQwFfNX*Ge%}Y;>Nh`|FD@n~uiBHQTZlX-=wL5gX77kjslfuKvKx$jEq$!LW!0$N>P)L`LHP literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/filters/__pycache__/submission.cpython-313.pyc b/frontend_focused/backend/submissions/filters/__pycache__/submission.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a45d7c65fd6a2fa1005840090bb8911a9ccef17a GIT binary patch literal 3107 zcmc&$&2JM&6yNpkI{seAc0x$fG@*fFNE|*AiqoVB3Q-^=h%BiJv9#KFH_j4!H_WaP zIpI?k!~uFhZ6&0hD#0zs9{LCLXuAhnQ_7*VhunY^_1ZVH>m;TL<<^n*&CGjmW@moy zYc|8-00V99hlQKBTNvgAnKYW<4mQ7l!6QauBzB5HPR}A1=AJ3fB@S`DgvU+smjopA zG9AnTM&dgeNyxCXp~hz0{RrobQA>_ZdN;^qCdp#&0aE99LOR~u{BjzY|Q0s z4YxK{xP05h3755`Sh8wniv`WFEgap!HB4J;_5rXwZJQ^cdmkS_Udc1Wq85omz8s$v zaK|o)q^31n*@(fG$Oq&p_@N*BWD12Wup9@re}M(+$k+CVULOc40J=HC5fnW%7DK zx8>}TlFw^KPRlGJ)37x|l@|dlOH(roN_H9M8EebNR>nck+QCb$tYV&A5VgS%$R#b} z$zL(`XnQT%SBdtm3H4a#!_NC&)C&T107#Zxq|qQBFeFwt^FSUkbKGH9qJksNvHkGP zHVorRNH_1sH|TH@n`WElo-{iM`!?}7hd1Kx)z2h(k`Q_tUMA(a z=-$%M4Yg?5XjPVX$`5YImo!BM`yq$~OU~+clcI;1i3ABECx6Og-qP}m*mtX_p;b$> zF;8PdxUJ-inne)mg@*cIfWkmIo;r7rG! zaQEKbXQA+uxk~8dvxo?NuO?HG97#>} zI}CO;6}OwIMnIdic{j5#%&fy`qO&`l!j+7E$uPrXS>>G?e7`-=of9&z; z@5jFzuXPPnx(0p;4%8F-A5Yf0Q`PR&`q@hNsY>G1PaU=NR5d+SzJ8;Up06b4*Dko; z@uawRw=9m7eIx(2J+Th8y;G*=xNIXlw*poXNx<&r0giR{3T2RjMQA`G;v#@+tdX*z z2ow!lmUA|6BhB2W?4DqfM1&8&1I(tfa0s-m<3LJGJs7<=P9x@dFbe%QXTCnOt~@+b zj-M`vPB(B;2u{b!zGMG4P7zYXnlK7%bI_-7rQKXDPSEN94|HgGBmwmI><@(6GC1os0#>K^QL0tOsH`rD_S`4`I3U1%ti|(4$Tw` z3!2NqKIffqQN>|5+e!3YfFtK2)UH8%IcssyjmZgQ7O>x)Tr+X_oN4AY#eJ&a5V*kk zt%rdRW;5hLKnoO2xQIj~qIH`9hDg-W3prP#iJ9agL3XcU78|O@hJG9?&)ulR z=1WucXiqIVSdD_tWF>mBbg>@qtHp<^@uBi3)0Oy4=`!8*XjME~K6#-cPLwA9<~>69 zD+b7GUyBfU9TS98uOotR{0)#n=hWn^Ngo=94xI&pqmC6)>O#tS71Q#BgVuK0citU- z2VQvM(F?zY1on+Nub9u!71)-fP3t2dFFBTFe`UlMOxtfv&!0@sU*04;_-(4zd7{#J L;tj;QbFhB^&hg}k literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/filters/submission.py b/frontend_focused/backend/submissions/filters/submission.py index c1ead3dc..76cfa3bf 100644 --- a/frontend_focused/backend/submissions/filters/submission.py +++ b/frontend_focused/backend/submissions/filters/submission.py @@ -1,18 +1,50 @@ +import datetime + import django_filters +from django.db.models import Q from submissions import models class SubmissionFilterSet(django_filters.FilterSet): - """Basic filter set for the submissions list endpoint. - - Only the status filter is implemented so the candidate can extend the - remaining filters (broker, company search, optional extras, etc.). - """ - status = django_filters.CharFilter(field_name="status", lookup_expr="iexact") + brokerId = django_filters.NumberFilter(field_name="broker_id") + companySearch = django_filters.CharFilter(method="filter_company_search") + createdFrom = django_filters.DateTimeFilter(field_name="created_at", lookup_expr="gte") + createdTo = django_filters.DateTimeFilter(method="filter_created_to") + hasDocuments = django_filters.BooleanFilter(method="filter_has_documents") + hasNotes = django_filters.BooleanFilter(method="filter_has_notes") class Meta: model = models.Submission fields = ["status"] + def filter_company_search(self, queryset, name, value): + if not value: + return queryset + return queryset.filter( + Q(company__legal_name__icontains=value) + | Q(company__industry__icontains=value) + | Q(company__headquarters_city__icontains=value) + ) + + def filter_created_to(self, queryset, name, value): + if not value: + return queryset + if value.time() == datetime.time.min: + value = value + datetime.timedelta(days=1) - datetime.timedelta(microseconds=1) + return queryset.filter(created_at__lte=value) + + def filter_has_documents(self, queryset, name, value): + if value is None: + return queryset + if value: + return queryset.filter(document_count__gt=0) + return queryset.filter(document_count=0) + + def filter_has_notes(self, queryset, name, value): + if value is None: + return queryset + if value: + return queryset.filter(note_count__gt=0) + return queryset.filter(note_count=0) diff --git a/frontend_focused/backend/submissions/management/__pycache__/__init__.cpython-313.pyc b/frontend_focused/backend/submissions/management/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2c27527d2264c1adffbebce7e14cc5e15d4371b GIT binary patch literal 234 zcmYk0JqiLb6oeBM6~Q}fV`B=nXu8hwuiTWm_*G>;=q% ziqp(|&CJxp;ebRvuGwK0{n@}*^b_iS3^%eRjudpvE)tqrF&=gFe#sGYuDkV)JS?;U}(g?^cFTE5jOZhP} p`|D)-H_QOg!~&R}&Q*V*ttgdTx^7%)Zu{o)t4%4b$i#Ibya7{AMsxrG literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/management/commands/__pycache__/__init__.cpython-313.pyc b/frontend_focused/backend/submissions/management/commands/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ea502dfe768be589dedb2d38989d989248577f7 GIT binary patch literal 238 zcmYk0u?@m75Jhc35FurUR7j!700_|_S{ho5Q(`;85ZjTR6tDpUFa#SgOIj8{$^u*v zNc`shZ{E8u45tM1xMs&W-e*64q2JO4SZ-uT94Tl#xI}BKb!tRZrTml@GRm!se3NPA zy|Tt9+nBZ!0AV@6ope2>-ES6*TcJ$BN~Ki|{E(KVG=*fjv!<3tKyG>CrC=FyakD=r sLm{fPG&N#Add~oqK@ud@xeb=uil&q+H%49my6@k94JoB9nYj^;H&hcx&Hw-a literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/management/commands/__pycache__/seed_submissions.cpython-313.pyc b/frontend_focused/backend/submissions/management/commands/__pycache__/seed_submissions.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f192bed6e07155d7abb556a4a81ecf035a5027e4 GIT binary patch literal 7417 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 GSn@Z{CU_M9 literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/migrations/__pycache__/0001_initial.cpython-313.pyc b/frontend_focused/backend/submissions/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6cad49a13106c43b8dcfdb34773eb5f1e80718f GIT binary patch literal 5261 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 CxS-+y literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/migrations/__pycache__/__init__.cpython-313.pyc b/frontend_focused/backend/submissions/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d777af0d12f573d806723d316e6d8905720330c GIT binary patch literal 182 zcmey&%ge<81UovDvOx4>5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|Lot5r;LeoAIu zdQ47cZe~e*az7<^c_e*DI*J#bJ}1pHiBWYFESxv<2jfVi4maGb1Bo5i^hl0D&7a AJOBUy literal 0 HcmV?d00001 diff --git a/frontend_focused/backend/submissions/tests.py b/frontend_focused/backend/submissions/tests.py new file mode 100644 index 00000000..cafbd388 --- /dev/null +++ b/frontend_focused/backend/submissions/tests.py @@ -0,0 +1,174 @@ +from datetime import timedelta + +from django.urls import reverse +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APITestCase + +from submissions import models + + +class SubmissionFixtureMixin: + def setUp(self): + super().setUp() + + self.broker_a = models.Broker.objects.create( + name="Alpha Brokerage", primary_contact_email="alpha@example.com" + ) + self.broker_b = models.Broker.objects.create( + name="Beta Brokerage", primary_contact_email="beta@example.com" + ) + + self.company_acme = models.Company.objects.create( + legal_name="Acme Robotics", industry="Manufacturing", headquarters_city="Denver" + ) + self.company_globex = models.Company.objects.create( + legal_name="Globex Logistics", industry="Transportation", headquarters_city="Reno" + ) + + self.owner = models.TeamMember.objects.create( + full_name="Jamie Rivera", email="jamie@example.com" + ) + + now = timezone.now() + + self.submission_new_acme = models.Submission.objects.create( + company=self.company_acme, + broker=self.broker_a, + owner=self.owner, + status=models.Submission.Status.NEW, + priority=models.Submission.Priority.HIGH, + summary="New opportunity with Acme", + created_at=now - timedelta(days=1), + ) + self.submission_review_globex = models.Submission.objects.create( + company=self.company_globex, + broker=self.broker_b, + owner=self.owner, + status=models.Submission.Status.IN_REVIEW, + priority=models.Submission.Priority.MEDIUM, + summary="Reviewing Globex logistics contract", + created_at=now - timedelta(days=10), + ) + self.submission_closed_acme = models.Submission.objects.create( + company=self.company_acme, + broker=self.broker_b, + owner=self.owner, + status=models.Submission.Status.CLOSED, + priority=models.Submission.Priority.LOW, + summary="Closed deal with Acme", + created_at=now - timedelta(days=30), + ) + + models.Document.objects.create( + submission=self.submission_new_acme, + title="Acme Proposal", + doc_type="Contract", + file_url="https://example.com/doc.pdf", + ) + models.Note.objects.create( + submission=self.submission_new_acme, + author_name="Jamie Rivera", + body="Initial call went well.", + created_at=now - timedelta(hours=2), + ) + models.Note.objects.create( + submission=self.submission_new_acme, + author_name="Jamie Rivera", + body="Follow-up scheduled for next week.", + created_at=now - timedelta(hours=1), + ) + models.Contact.objects.create( + submission=self.submission_review_globex, + name="Pat Lee", + role="VP Operations", + email="pat@globex.com", + phone="555-0100", + ) + + +class SubmissionListFilterTests(SubmissionFixtureMixin, APITestCase): + def get_ids(self, response): + return {item["id"] for item in response.data["results"]} + + def test_list_returns_all_by_default(self): + response = self.client.get(reverse("submission-list")) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["count"], 3) + + def test_filter_by_status(self): + response = self.client.get(reverse("submission-list"), {"status": "new"}) + self.assertEqual(self.get_ids(response), {self.submission_new_acme.id}) + + def test_filter_by_broker_id(self): + response = self.client.get(reverse("submission-list"), {"brokerId": self.broker_b.id}) + self.assertEqual( + self.get_ids(response), + {self.submission_review_globex.id, self.submission_closed_acme.id}, + ) + + def test_filter_by_company_search_matches_name_industry_or_city(self): + response = self.client.get(reverse("submission-list"), {"companySearch": "acme"}) + self.assertEqual( + self.get_ids(response), {self.submission_new_acme.id, self.submission_closed_acme.id} + ) + + response = self.client.get(reverse("submission-list"), {"companySearch": "reno"}) + self.assertEqual(self.get_ids(response), {self.submission_review_globex.id}) + + def test_filter_by_created_from_and_to(self): + five_days_ago = (timezone.now() - timedelta(days=5)).date().isoformat() + response = self.client.get(reverse("submission-list"), {"createdFrom": five_days_ago}) + self.assertEqual(self.get_ids(response), {self.submission_new_acme.id}) + + response = self.client.get(reverse("submission-list"), {"createdTo": five_days_ago}) + self.assertEqual( + self.get_ids(response), + {self.submission_review_globex.id, self.submission_closed_acme.id}, + ) + + def test_filter_has_documents(self): + response = self.client.get(reverse("submission-list"), {"hasDocuments": "true"}) + self.assertEqual(self.get_ids(response), {self.submission_new_acme.id}) + + response = self.client.get(reverse("submission-list"), {"hasDocuments": "false"}) + self.assertEqual( + self.get_ids(response), + {self.submission_review_globex.id, self.submission_closed_acme.id}, + ) + + def test_filter_has_notes(self): + response = self.client.get(reverse("submission-list"), {"hasNotes": "true"}) + self.assertEqual(self.get_ids(response), {self.submission_new_acme.id}) + + def test_list_includes_counts_and_latest_note(self): + response = self.client.get(reverse("submission-list"), {"status": "new"}) + payload = response.data["results"][0] + self.assertEqual(payload["document_count"], 1) + self.assertEqual(payload["note_count"], 2) + self.assertEqual( + payload["latest_note"]["body_preview"], "Follow-up scheduled for next week." + ) + + +class SubmissionDetailTests(SubmissionFixtureMixin, APITestCase): + def test_detail_includes_nested_relations(self): + response = self.client.get( + reverse("submission-detail", args=[self.submission_review_globex.id]) + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["contacts"]), 1) + self.assertEqual(response.data["contacts"][0]["name"], "Pat Lee") + self.assertEqual(response.data["company"]["legal_name"], "Globex Logistics") + + def test_detail_404_for_missing_submission(self): + response = self.client.get(reverse("submission-detail", args=[999999])) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + +class BrokerEndpointTests(SubmissionFixtureMixin, APITestCase): + def test_brokers_list_is_not_paginated(self): + response = self.client.get(reverse("broker-list")) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsInstance(response.data, list) + self.assertEqual(len(response.data), 2) diff --git a/frontend_focused/backend/submissions/views.py b/frontend_focused/backend/submissions/views.py index 53f6f400..423b0e8b 100644 --- a/frontend_focused/backend/submissions/views.py +++ b/frontend_focused/backend/submissions/views.py @@ -14,12 +14,16 @@ def get_queryset(self): if self.action == "list": latest_note = models.Note.objects.filter(submission_id=OuterRef("pk")).order_by("-created_at") - queryset = queryset.annotate( + queryset = queryset.select_related("broker", "company", "owner").annotate( document_count=Count("documents", distinct=True), note_count=Count("notes", distinct=True), latest_note_author=Subquery(latest_note.values("author_name")[:1]), latest_note_body=Subquery(latest_note.values("body")[:1]), latest_note_created_at=Subquery(latest_note.values("created_at")[:1]), + ).order_by("-created_at") + elif self.action == "retrieve": + queryset = queryset.select_related("broker", "company", "owner").prefetch_related( + "contacts", "documents", "notes" ) return queryset @@ -33,4 +37,5 @@ def get_serializer_class(self): class BrokerViewSet(viewsets.ReadOnlyModelViewSet): queryset = models.Broker.objects.all() serializer_class = serializers.BrokerSerializer + pagination_class = None diff --git a/frontend_focused/frontend/app/submissions/[id]/page.tsx b/frontend_focused/frontend/app/submissions/[id]/page.tsx index 5f95027b..a6efa4ec 100644 --- a/frontend_focused/frontend/app/submissions/[id]/page.tsx +++ b/frontend_focused/frontend/app/submissions/[id]/page.tsx @@ -8,51 +8,202 @@ import { Divider, Link as MuiLink, Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, Typography, } from '@mui/material'; import Link from 'next/link'; import { useParams } from 'next/navigation'; +import { QueryFeedback } from '@/components/QueryFeedback'; +import { PriorityChip, StatusChip } from '@/components/StatusChip'; +import { formatDate } from '@/lib/format'; import { useSubmissionDetail } from '@/lib/hooks/useSubmissions'; +function DetailField({ label, value }: { label: string; value: string }) { + return ( + + + {label} + + {value} + + ); +} + export default function SubmissionDetailPage() { const params = useParams<{ id: string }>(); const submissionId = params?.id ?? ''; const detailQuery = useSubmissionDetail(submissionId); + const submission = detailQuery.data; return ( - +
- Submission detail + + {submission ? submission.company.legalName : 'Submission detail'} + - Use this page to present the full submission payload along with contacts, documents, - and notes. + Full submission record with contacts, documents, and notes.
- - Back to list + + ← Back to list
- - - - API data placeholder - - - The React Query call is disabled until you turn it on. Once you enable it and wire up - serializers on the backend you can render key facts, contacts, documents, and note - timelines. - - -
-              {JSON.stringify({ submissionId, queryKey: detailQuery.queryKey }, null, 2)}
-            
-
-
+ detailQuery.refetch()} + > + {submission && ( + + + + + + + + + + {submission.summary} + + + + + + + + + + + + + + + + + + + + + Contacts ({submission.contacts.length}) + + {submission.contacts.length === 0 ? ( + No contacts on file. + ) : ( + + + + + Name + Role + Email + Phone + + + + {submission.contacts.map((contact) => ( + + {contact.name} + {contact.role} + {contact.email} + {contact.phone || '—'} + + ))} + +
+
+ )} +
+
+ + + + + Documents ({submission.documents.length}) + + {submission.documents.length === 0 ? ( + No documents attached. + ) : ( + + {submission.documents.map((doc) => ( + + + + {doc.title} + + + {doc.docType} · uploaded {formatDate(doc.uploadedAt)} + + + + ))} + + )} + + + + + + + Notes ({submission.notes.length}) + + {submission.notes.length === 0 ? ( + No notes yet. + ) : ( + }> + {submission.notes.map((note) => ( + + {note.authorName} + + {formatDate(note.createdAt)} + + + {note.body} + + + ))} + + )} + + +
+ )} +
); diff --git a/frontend_focused/frontend/app/submissions/page.tsx b/frontend_focused/frontend/app/submissions/page.tsx index 7473705b..c5f667ce 100644 --- a/frontend_focused/frontend/app/submissions/page.tsx +++ b/frontend_focused/frontend/app/submissions/page.tsx @@ -2,17 +2,32 @@ import { Box, + Button, Card, CardContent, + CircularProgress, + Collapse, Container, - Divider, + Link as MuiLink, MenuItem, + Pagination, Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, TextField, Typography, } from '@mui/material'; -import { useMemo, useState } from 'react'; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Suspense, useCallback, useEffect, useMemo, useState } from 'react'; +import { QueryFeedback } from '@/components/QueryFeedback'; +import { PriorityChip, StatusChip } from '@/components/StatusChip'; +import { formatDate, formatRelativeDate } from '@/lib/format'; import { useBrokerOptions } from '@/lib/hooks/useBrokerOptions'; import { useSubmissionsList } from '@/lib/hooks/useSubmissions'; import { SubmissionStatus } from '@/lib/types'; @@ -25,23 +40,190 @@ const STATUS_OPTIONS: { label: string; value: SubmissionStatus | '' }[] = [ { label: 'Lost', value: 'lost' }, ]; +const PAGE_SIZE = 10; + +const TRISTATE_OPTIONS: { label: string; value: '' | 'true' | 'false' }[] = [ + { label: 'Any', value: '' }, + { label: 'Yes', value: 'true' }, + { label: 'No', value: 'false' }, +]; + +function useDebouncedValue(value: T, delay: number): T { + const [debounced, setDebounced] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debounced; +} + export default function SubmissionsPage() { - const [status, setStatus] = useState(''); - const [brokerId, setBrokerId] = useState(''); - const [companyQuery, setCompanyQuery] = useState(''); + return ( + + + + + + } + > + + + ); +} + +function SubmissionsPageContent() { + const router = useRouter(); + const searchParams = useSearchParams(); + + const [status, setStatus] = useState( + () => (searchParams.get('status') as SubmissionStatus) || '', + ); + const [brokerId, setBrokerId] = useState(() => searchParams.get('brokerId') ?? ''); + const [companyQuery, setCompanyQuery] = useState(() => searchParams.get('companySearch') ?? ''); + const [createdFrom, setCreatedFrom] = useState(() => searchParams.get('createdFrom') ?? ''); + const [createdTo, setCreatedTo] = useState(() => searchParams.get('createdTo') ?? ''); + const [hasDocuments, setHasDocuments] = useState<'' | 'true' | 'false'>( + () => (searchParams.get('hasDocuments') as '' | 'true' | 'false') || '', + ); + const [hasNotes, setHasNotes] = useState<'' | 'true' | 'false'>( + () => (searchParams.get('hasNotes') as '' | 'true' | 'false') || '', + ); + const [showMoreFilters, setShowMoreFilters] = useState(() => + Boolean(createdFrom || createdTo || hasDocuments || hasNotes), + ); + const [page, setPage] = useState(() => Number(searchParams.get('page') ?? '1') || 1); + + const debouncedCompanySearch = useDebouncedValue(companyQuery, 400); const filters = useMemo( () => ({ status: status || undefined, brokerId: brokerId || undefined, - companySearch: companyQuery || undefined, + companySearch: debouncedCompanySearch || undefined, + createdFrom: createdFrom || undefined, + createdTo: createdTo || undefined, + hasDocuments: hasDocuments ? hasDocuments === 'true' : undefined, + hasNotes: hasNotes ? hasNotes === 'true' : undefined, + page: page > 1 ? page : undefined, }), - [status, brokerId, companyQuery], + [ + status, + brokerId, + debouncedCompanySearch, + createdFrom, + createdTo, + hasDocuments, + hasNotes, + page, + ], ); const submissionsQuery = useSubmissionsList(filters); const brokerQuery = useBrokerOptions(); + const syncUrl = useCallback( + (next: { + status: string; + brokerId: string; + companySearch: string; + createdFrom: string; + createdTo: string; + hasDocuments: string; + hasNotes: string; + page: number; + }) => { + const params = new URLSearchParams(); + if (next.status) params.set('status', next.status); + if (next.brokerId) params.set('brokerId', next.brokerId); + if (next.companySearch) params.set('companySearch', next.companySearch); + if (next.createdFrom) params.set('createdFrom', next.createdFrom); + if (next.createdTo) params.set('createdTo', next.createdTo); + if (next.hasDocuments) params.set('hasDocuments', next.hasDocuments); + if (next.hasNotes) params.set('hasNotes', next.hasNotes); + if (next.page > 1) params.set('page', String(next.page)); + + const query = params.toString(); + router.replace(query ? `/submissions?${query}` : '/submissions', { scroll: false }); + }, + [router], + ); + + useEffect(() => { + syncUrl({ + status, + brokerId, + companySearch: debouncedCompanySearch, + createdFrom, + createdTo, + hasDocuments, + hasNotes, + page, + }); + }, [ + status, + brokerId, + debouncedCompanySearch, + createdFrom, + createdTo, + hasDocuments, + hasNotes, + page, + syncUrl, + ]); + + const handleStatusChange = (value: SubmissionStatus | '') => { + setStatus(value); + setPage(1); + }; + + const handleBrokerChange = (value: string) => { + setBrokerId(value); + setPage(1); + }; + + const handleCompanyChange = (value: string) => { + setCompanyQuery(value); + setPage(1); + }; + + const handleCreatedFromChange = (value: string) => { + setCreatedFrom(value); + setPage(1); + }; + + const handleCreatedToChange = (value: string) => { + setCreatedTo(value); + setPage(1); + }; + + const handleHasDocumentsChange = (value: '' | 'true' | 'false') => { + setHasDocuments(value); + setPage(1); + }; + + const handleHasNotesChange = (value: '' | 'true' | 'false') => { + setHasNotes(value); + setPage(1); + }; + + const hasAdvancedFilters = Boolean(createdFrom || createdTo || hasDocuments || hasNotes); + + const handleClearAdvancedFilters = () => { + setCreatedFrom(''); + setCreatedTo(''); + setHasDocuments(''); + setHasNotes(''); + setPage(1); + }; + + const totalCount = submissionsQuery.data?.count ?? 0; + const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); + const results = submissionsQuery.data?.results ?? []; + return ( @@ -50,8 +232,8 @@ export default function SubmissionsPage() { Submissions - Filters update the query parameters and drive backend filtering. Hook these inputs to - your API calls when you implement the actual data fetching. + Review incoming broker opportunities. Filters sync to the URL so you can share or + bookmark a view. @@ -62,7 +244,9 @@ export default function SubmissionsPage() { select label="Status" value={status} - onChange={(event) => setStatus(event.target.value as SubmissionStatus | '')} + onChange={(event) => + handleStatusChange(event.target.value as SubmissionStatus | '') + } fullWidth > {STATUS_OPTIONS.map((option) => ( @@ -75,9 +259,10 @@ export default function SubmissionsPage() { select label="Broker" value={brokerId} - onChange={(event) => setBrokerId(event.target.value)} + onChange={(event) => handleBrokerChange(event.target.value)} fullWidth - helperText="Populate options via /api/brokers" + disabled={brokerQuery.isLoading} + helperText={brokerQuery.isError ? 'Could not load brokers' : undefined} > All brokers {brokerQuery.data?.map((broker) => ( @@ -89,28 +274,185 @@ export default function SubmissionsPage() { setCompanyQuery(event.target.value)} + onChange={(event) => handleCompanyChange(event.target.value)} fullWidth - helperText="Send as ?companySearch=..." + placeholder="Search by name, industry, or city" /> + + + + + + + + handleCreatedFromChange(event.target.value)} + fullWidth + slotProps={{ inputLabel: { shrink: true } }} + /> + handleCreatedToChange(event.target.value)} + fullWidth + slotProps={{ inputLabel: { shrink: true } }} + /> + + handleHasDocumentsChange(event.target.value as '' | 'true' | 'false') + } + fullWidth + > + {TRISTATE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + handleHasNotesChange(event.target.value as '' | 'true' | 'false') + } + fullWidth + > + {TRISTATE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + - Submission list - - Hook `submissionsQuery` to render rows, totals, and pagination states. The query is - disabled by default so no network calls fire until you enable it. - - - -
-                  {JSON.stringify({ filters, queryKey: submissionsQuery.queryKey }, null, 2)}
-                
+ + Submission list + {!submissionsQuery.isLoading && !submissionsQuery.isError && ( + + {totalCount} submission{totalCount === 1 ? '' : 's'} + + )} + + submissionsQuery.refetch()} + > + + + + + Company + Broker + Status + Priority + Owner + Docs + Notes + Latest note + Created + + + + {results.map((submission) => ( + + + + {submission.company.legalName} + + + {submission.company.industry} · {submission.company.headquartersCity} + + + {submission.broker.name} + + + + + + + {submission.owner.fullName} + {submission.documentCount} + {submission.noteCount} + + {submission.latestNote ? ( + <> + + {submission.latestNote.bodyPreview} + + + {submission.latestNote.authorName} ·{' '} + {formatRelativeDate(submission.latestNote.createdAt)} + + + ) : ( + + — + + )} + + {formatDate(submission.createdAt)} + + ))} + +
+
+ + {totalPages > 1 && ( + + setPage(value)} + color="primary" + /> + + )} +
diff --git a/frontend_focused/frontend/components/QueryFeedback.tsx b/frontend_focused/frontend/components/QueryFeedback.tsx new file mode 100644 index 00000000..4e74449f --- /dev/null +++ b/frontend_focused/frontend/components/QueryFeedback.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/material'; +import { PropsWithChildren } from 'react'; + +interface QueryFeedbackProps { + isLoading: boolean; + isError: boolean; + errorMessage?: string; + isEmpty?: boolean; + emptyMessage?: string; + onRetry?: () => void; +} + +export function QueryFeedback({ + isLoading, + isError, + errorMessage, + isEmpty = false, + emptyMessage = 'No results found.', + onRetry, + children, +}: PropsWithChildren) { + if (isLoading) { + return ( + + + + ); + } + + if (isError) { + return ( + + Retry + + ) : undefined + } + > + {errorMessage ?? 'Something went wrong while loading data.'} + + ); + } + + if (isEmpty) { + return ( + + {emptyMessage} + + ); + } + + return <>{children}; +} diff --git a/frontend_focused/frontend/components/StatusChip.tsx b/frontend_focused/frontend/components/StatusChip.tsx new file mode 100644 index 00000000..bacdc944 --- /dev/null +++ b/frontend_focused/frontend/components/StatusChip.tsx @@ -0,0 +1,45 @@ +import { Chip } from '@mui/material'; + +import { formatPriority, formatStatus } from '@/lib/format'; +import { SubmissionPriority, SubmissionStatus } from '@/lib/types'; + +const STATUS_COLORS: Record< + SubmissionStatus, + 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' +> = { + new: 'info', + in_review: 'warning', + closed: 'success', + lost: 'error', +}; + +const PRIORITY_COLORS: Record< + SubmissionPriority, + 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' +> = { + high: 'error', + medium: 'warning', + low: 'default', +}; + +export function StatusChip({ status }: { status: SubmissionStatus }) { + return ( + + ); +} + +export function PriorityChip({ priority }: { priority: SubmissionPriority }) { + return ( + + ); +} diff --git a/frontend_focused/frontend/lib/format.ts b/frontend_focused/frontend/lib/format.ts new file mode 100644 index 00000000..1c9c56ba --- /dev/null +++ b/frontend_focused/frontend/lib/format.ts @@ -0,0 +1,41 @@ +import { SubmissionPriority, SubmissionStatus } from '@/lib/types'; + +const STATUS_LABELS: Record = { + new: 'New', + in_review: 'In Review', + closed: 'Closed', + lost: 'Lost', +}; + +const PRIORITY_LABELS: Record = { + high: 'High', + medium: 'Medium', + low: 'Low', +}; + +export function formatStatus(status: SubmissionStatus): string { + return STATUS_LABELS[status] ?? status; +} + +export function formatPriority(priority: SubmissionPriority): string { + return PRIORITY_LABELS[priority] ?? priority; +} + +export function formatDate(value: string): string { + return new Intl.DateTimeFormat('en-US', { + dateStyle: 'medium', + timeStyle: 'short', + }).format(new Date(value)); +} + +export function formatRelativeDate(value: string): string { + const date = new Date(value); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Yesterday'; + if (diffDays < 7) return `${diffDays} days ago`; + return formatDate(value); +} diff --git a/frontend_focused/frontend/lib/hooks/useBrokerOptions.ts b/frontend_focused/frontend/lib/hooks/useBrokerOptions.ts index 4574af18..ac8769c1 100644 --- a/frontend_focused/frontend/lib/hooks/useBrokerOptions.ts +++ b/frontend_focused/frontend/lib/hooks/useBrokerOptions.ts @@ -14,6 +14,6 @@ export function useBrokerOptions() { return useQuery({ queryKey: ['brokers'], queryFn: fetchBrokers, - enabled: false, + staleTime: 5 * 60_000, }); } diff --git a/frontend_focused/frontend/lib/hooks/useSubmissions.ts b/frontend_focused/frontend/lib/hooks/useSubmissions.ts index 297d22f4..d1ae9669 100644 --- a/frontend_focused/frontend/lib/hooks/useSubmissions.ts +++ b/frontend_focused/frontend/lib/hooks/useSubmissions.ts @@ -19,6 +19,11 @@ async function fetchSubmissions(filters: SubmissionListFilters) { status: filters.status, brokerId: filters.brokerId, companySearch: filters.companySearch, + createdFrom: filters.createdFrom, + createdTo: filters.createdTo, + hasDocuments: filters.hasDocuments, + hasNotes: filters.hasNotes, + page: filters.page, }, }); return response.data; @@ -37,7 +42,7 @@ export function useSubmissionsList(filters: SubmissionListFilters) { return useQuery({ queryKey: [SUBMISSIONS_QUERY_KEY, filters] as QueryKey, queryFn: () => fetchSubmissions(filters), - enabled: false, + placeholderData: (previous) => previous, }); } @@ -45,7 +50,7 @@ export function useSubmissionDetail(id: string | number) { return useQuery({ queryKey: [SUBMISSIONS_QUERY_KEY, id], queryFn: () => fetchSubmissionDetail(id), - enabled: false, + enabled: Boolean(id), staleTime: 60_000, }); } diff --git a/frontend_focused/frontend/lib/types.ts b/frontend_focused/frontend/lib/types.ts index d845fd78..34fc001e 100644 --- a/frontend_focused/frontend/lib/types.ts +++ b/frontend_focused/frontend/lib/types.ts @@ -84,4 +84,9 @@ export interface SubmissionListFilters { status?: SubmissionStatus; brokerId?: string; companySearch?: string; + createdFrom?: string; + createdTo?: string; + hasDocuments?: boolean; + hasNotes?: boolean; + page?: number; }