From e0dfb55c5457aec489fc084c4567e791b4c65eb1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 26 Mar 2025 00:37:41 +0000 Subject: 3/25 까지의 대표님 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 18 + .env.production | 18 + .gitignore | 61 + README.md | 62 + app/[lng]/evcp/bqtbe/page.tsx | 72 + app/[lng]/evcp/budgetary/[id]/cbe/page.tsx | 56 + app/[lng]/evcp/budgetary/[id]/layout.tsx | 80 + app/[lng]/evcp/budgetary/[id]/page.tsx | 57 + app/[lng]/evcp/budgetary/[id]/tbe/page.tsx | 55 + app/[lng]/evcp/budgetary/page.tsx | 86 + app/[lng]/evcp/equip-class/page.tsx | 75 + app/[lng]/evcp/faq/manage/actions.ts | 48 + app/[lng]/evcp/faq/manage/page.tsx | 38 + app/[lng]/evcp/faq/page.tsx | 62 + app/[lng]/evcp/form-list/page.tsx | 75 + app/[lng]/evcp/items/page.tsx | 74 + app/[lng]/evcp/layout.tsx | 17 + app/[lng]/evcp/page.tsx | 8 + app/[lng]/evcp/po/page.tsx | 65 + app/[lng]/evcp/pq-criteria/page.tsx | 71 + app/[lng]/evcp/pq/[vendorId]/page.tsx | 38 + app/[lng]/evcp/pq/page.tsx | 71 + app/[lng]/evcp/report/page.tsx | 8 + app/[lng]/evcp/rfq/[id]/cbe/page.tsx | 53 + app/[lng]/evcp/rfq/[id]/layout.tsx | 80 + app/[lng]/evcp/rfq/[id]/page.tsx | 55 + app/[lng]/evcp/rfq/[id]/tbe/page.tsx | 55 + app/[lng]/evcp/rfq/page.tsx | 80 + app/[lng]/evcp/settings/layout.tsx | 68 + app/[lng]/evcp/settings/page.tsx | 18 + app/[lng]/evcp/settings/preferences/page.tsx | 17 + app/[lng]/evcp/system/admin-users/page.tsx | 60 + app/[lng]/evcp/system/layout.tsx | 75 + app/[lng]/evcp/system/page.tsx | 56 + app/[lng]/evcp/system/permissions/page.tsx | 17 + app/[lng]/evcp/system/roles/page.tsx | 68 + app/[lng]/evcp/tag-numbering/page.tsx | 74 + app/[lng]/evcp/tasks/page.tsx | 63 + app/[lng]/evcp/vendors/[id]/info/items/page.tsx | 56 + app/[lng]/evcp/vendors/[id]/info/layout.tsx | 79 + app/[lng]/evcp/vendors/[id]/info/page.tsx | 56 + .../evcp/vendors/[id]/info/rfq-history/page.tsx | 55 + app/[lng]/evcp/vendors/page.tsx | 78 + app/[lng]/login/page.tsx | 15 + app/[lng]/partners/(partners)/dashboard/page.tsx | 8 + .../(partners)/document-list/[contractId]/page.tsx | 44 + .../partners/(partners)/document-list/layout.tsx | 45 + .../partners/(partners)/document-list/page.tsx | 21 + .../(partners)/documents/[contractId]/page.tsx | 47 + app/[lng]/partners/(partners)/documents/layout.tsx | 44 + app/[lng]/partners/(partners)/documents/page.tsx | 21 + app/[lng]/partners/(partners)/layout.tsx | 17 + app/[lng]/partners/(partners)/rfq/page.tsx | 133 + app/[lng]/partners/(partners)/system/page.tsx | 8 + app/[lng]/partners/(partners)/tbe/page.tsx | 85 + .../vendor-data/form/[packageId]/[formId]/page.tsx | 41 + .../partners/(partners)/vendor-data/layout.tsx | 69 + app/[lng]/partners/(partners)/vendor-data/page.tsx | 29 + .../(partners)/vendor-data/tag/[id]/page.tsx | 43 + app/[lng]/partners/page.tsx | 21 + app/[lng]/partners/pq/page.tsx | 39 + app/[lng]/partners/repository/page.tsx | 11 + app/[lng]/partners/signup/page.tsx | 21 + app/[lng]/qna/layout.tsx | 18 + app/[lng]/qna/page.tsx | 8 + app/api/auth/[...nextauth]/route.ts | 111 + app/api/files/[...path]/route.ts | 74 + app/api/rfq-download/route.ts | 121 + app/api/rfq-upload/route.ts | 36 + app/api/tbe-download/route.ts | 121 + app/api/upload/route.ts | 38 + app/api/vendors/attachments/download-temp/route.ts | 102 + app/api/vendors/erp/route.ts | 144 + app/favicon.ico | Bin 0 -> 25931 bytes app/globals.css | 168 + app/layout.tsx | 85 + app/page.tsx | 101 + atoms.ts | 3 + components.json | 21 + components/ProjectSelector.tsx | 124 + .../data-table-column-simple-header.tsx | 61 + .../client-data-table/data-table-filter-list.tsx | 662 + .../client-data-table/data-table-group-list.tsx | 279 + .../client-data-table/data-table-pagination.tsx | 132 + .../client-data-table/data-table-resizer.tsx | 119 + .../client-data-table/data-table-sort-list.tsx | 272 + .../client-data-table/data-table-toolbar.tsx | 100 + .../client-data-table/data-table-view-options.tsx | 192 + components/client-data-table/data-table.tsx | 336 + .../data-table/data-table-advanced-toolbar.tsx | 104 + components/data-table/data-table-column-header.tsx | 109 + .../data-table/data-table-column-resizable.tsx | 57 + .../data-table/data-table-column-simple-header.tsx | 61 + .../data-table/data-table-faceted-filter.tsx | 151 + components/data-table/data-table-filter-list.tsx | 787 ++ components/data-table/data-table-grobal-filter.tsx | 49 + components/data-table/data-table-group-list.tsx | 317 + components/data-table/data-table-pagination.tsx | 132 + components/data-table/data-table-pin-left.tsx | 95 + components/data-table/data-table-pin-right.tsx | 88 + components/data-table/data-table-pin.tsx | 146 + components/data-table/data-table-resizer.tsx | 98 + components/data-table/data-table-skeleton.tsx | 169 + components/data-table/data-table-sort-list.tsx | 370 + components/data-table/data-table-toolbar.tsx | 119 + components/data-table/data-table-view-options.tsx | 191 + components/data-table/data-table.tsx | 209 + components/date-range-picker.tsx | 146 + .../document-lists/vendor-doc-list-client.tsx | 81 + components/documents/RevisionForm.tsx | 115 + components/documents/StageList.tsx | 256 + components/documents/StageListfromSHI.tsx | 187 + components/documents/add-document-dialog.tsx | 515 + components/documents/document-container.tsx | 85 + components/documents/project-swicher.tsx | 138 + components/documents/vendor-docs.client.tsx | 80 + components/documents/view-document-dialog.tsx | 246 + components/faq/FaqCard.tsx | 36 + components/faq/FaqManager.tsx | 192 + components/form-data/form-data-table-columns.tsx | 138 + components/form-data/form-data-table.tsx | 545 + components/form-data/update-form-sheet.tsx | 239 + components/kbd.tsx | 54 + components/layout/Footer.tsx | 16 + components/layout/Header.tsx | 225 + components/layout/MobileMenu.tsx | 88 + components/layout/command-menu.tsx | 139 + components/layout/createEmotionCashe.ts | 5 + components/layout/mode-switcher.tsx | 35 + components/layout/providers.tsx | 38 + components/layout/sidebar-nav.tsx | 44 + components/login/login-form-skeleton.tsx | 71 + components/login/login-form.tsx | 323 + components/login/partner-auth-form.tsx | 241 + components/pq/pq-input-tabs.tsx | 780 ++ components/pq/pq-review-detail.tsx | 712 + components/pq/pq-review-table.tsx | 340 + components/settings/account-form.tsx | 263 + components/settings/appearance-form.tsx | 244 + components/shell.tsx | 37 + components/signup/join-form-skeleton.tsx | 75 + components/signup/join-form.tsx | 1010 ++ components/system/permissionDialog.tsx | 301 + components/system/permissionsTree.tsx | 167 + components/ui/accordion.tsx | 57 + components/ui/action-dialog.tsx | 54 + components/ui/alert-dialog.tsx | 141 + components/ui/alert.tsx | 59 + components/ui/aspect-ratio.tsx | 7 + components/ui/avatar.tsx | 50 + components/ui/badge.tsx | 36 + components/ui/breadcrumb.tsx | 115 + components/ui/button.tsx | 60 + components/ui/calendar.tsx | 76 + components/ui/card.tsx | 76 + components/ui/carousel.tsx | 262 + components/ui/chart.tsx | 365 + components/ui/checkbox.tsx | 30 + components/ui/collapsible.tsx | 11 + components/ui/command.tsx | 153 + components/ui/context-menu.tsx | 200 + components/ui/dialog.tsx | 122 + components/ui/drawer.tsx | 118 + components/ui/dropdown-menu.tsx | 201 + components/ui/dropzone-primitive.tsx | 192 + components/ui/dropzone.tsx | 87 + components/ui/faceted-filter.tsx | 106 + components/ui/file-list.tsx | 173 + components/ui/form.tsx | 178 + components/ui/hover-card.tsx | 29 + components/ui/input-otp.tsx | 71 + components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/menubar.tsx | 236 + components/ui/multi-select.tsx | 379 + components/ui/navigation-menu.tsx | 128 + components/ui/pagination.tsx | 117 + components/ui/popover.tsx | 33 + components/ui/portal.tsx | 7 + components/ui/progress.tsx | 28 + components/ui/radio-group.tsx | 44 + components/ui/resizable.tsx | 45 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 159 + components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 140 + components/ui/sidebar.tsx | 763 ++ components/ui/skeleton.tsx | 15 + components/ui/slider.tsx | 28 + components/ui/sonner.tsx | 31 + components/ui/sortable.tsx | 336 + components/ui/switch.tsx | 29 + components/ui/table.tsx | 120 + components/ui/tabs.tsx | 55 + components/ui/textarea.tsx | 22 + components/ui/toast.tsx | 129 + components/ui/toaster.tsx | 35 + components/ui/toasterSonner.tsx | 32 + components/ui/toggle-group.tsx | 61 + components/ui/toggle.tsx | 45 + components/ui/tooltip.tsx | 32 + components/vendor-data/project-swicher.tsx | 116 + components/vendor-data/sidebar.tsx | 235 + .../vendor-data/tag-table/add-tag-dialog.tsx | 357 + .../vendor-data/tag-table/tag-table-column.tsx | 196 + components/vendor-data/tag-table/tag-table.tsx | 39 + .../vendor-data/tag-table/tag-type-definitions.ts | 87 + components/vendor-data/vendor-data-container.tsx | 231 + config/data-table.ts | 94 + config/equipClassColumnsConfig.ts | 44 + config/euserColumnsConfig.ts | 58 + config/faqDataConfig.ts | 304 + config/formListsColumnsConfig.ts | 49 + config/itemsColumnsConfig.ts | 42 + config/language.ts | 13 + config/menuConfig.ts | 274 + config/permissionsConfig.ts | 10 + config/poColumnsConfig.ts | 180 + config/rfqHistoryColumnsConfig.ts | 73 + config/rfqsColumnsConfig.ts | 64 + config/rfqsVendorColumnsConfig.ts | 45 + config/roleColumnsConfig.ts | 62 + config/site.ts | 15 + config/tagNumberingColumnsConfig.ts | 68 + config/tasksColumnsConfig.ts | 52 + config/userColumnsConfig.ts | 58 + config/vendorCbeColumnsConfig.ts | 156 + config/vendorColumnsConfig.ts | 70 + config/vendorContactsColumnsConfig.ts | 70 + config/vendorItemsColumnsConfig.ts | 59 + config/vendorRfbColumnsConfig.ts | 95 + config/vendorTbeColumnsConfig.ts | 241 + db/db.ts | 12 + db/migrations/0000_dusty_skaar.sql | 51 + db/migrations/0001_robust_landau.sql | 32 + db/migrations/0002_puzzling_johnny_blaze.sql | 1 + db/migrations/0003_unknown_speed_demon.sql | 5 + db/migrations/0004_cheerful_stark_industries.sql | 2 + db/migrations/0005_loud_whizzer.sql | 1 + db/migrations/0006_tan_mongu.sql | 2 + db/migrations/0007_big_franklin_storm.sql | 37 + db/migrations/0008_previous_omega_red.sql | 1 + db/migrations/0009_aromatic_falcon.sql | 1 + db/migrations/0010_big_sue_storm.sql | 92 + db/migrations/0011_tense_madame_web.sql | 3 + db/migrations/0012_giant_sunfire.sql | 6 + db/migrations/0013_exotic_blazing_skull.sql | 1 + db/migrations/0014_clammy_power_man.sql | 14 + db/migrations/0015_black_fixer.sql | 18 + db/migrations/0016_fat_komodo.sql | 5 + db/migrations/0017_thin_chimera.sql | 1 + db/migrations/0018_tiny_robbie_robertson.sql | 2 + db/migrations/0019_bumpy_anthem.sql | 3 + db/migrations/0020_chubby_carnage.sql | 2 + db/migrations/0021_nappy_dexter_bennett.sql | 1 + db/migrations/0022_panoramic_mad_thinker.sql | 8 + db/migrations/0023_familiar_raza.sql | 56 + db/migrations/0024_eager_bill_hollister.sql | 2 + db/migrations/0025_complex_husk.sql | 1 + db/migrations/0026_tiresome_jackpot.sql | 2 + db/migrations/0027_yummy_senator_kelly.sql | 1 + db/migrations/0028_eminent_shockwave.sql | 2 + db/migrations/0029_oval_carmella_unuscione.sql | 2 + db/migrations/0030_zippy_the_order.sql | 16 + db/migrations/0031_curvy_starhawk.sql | 1 + db/migrations/0032_unusual_speedball.sql | 1 + db/migrations/0033_chemical_maggott.sql | 2 + db/migrations/0035_sweet_revanche.sql | 2 + db/migrations/0036_aberrant_marvel_apes.sql | 9 + db/migrations/0037_legal_pretty_boy.sql | 9 + db/migrations/0038_tough_mimic.sql | 2 + db/migrations/0039_elite_zemo.sql | 3 + db/migrations/0040_absurd_human_cannonball.sql | 15 + db/migrations/0041_absurd_spyke.sql | 1 + db/migrations/0042_panoramic_daimon_hellstrom.sql | 12 + db/migrations/0043_pale_black_bird.sql | 3 + db/migrations/0044_lush_tenebrous.sql | 2 + db/migrations/0045_complex_gamora.sql | 2 + db/migrations/0046_overjoyed_tinkerer.sql | 2 + db/migrations/0047_broad_the_executioner.sql | 34 + db/migrations/0048_strange_ultimo.sql | 16 + db/migrations/0049_green_mattie_franklin.sql | 3 + db/migrations/0050_furry_black_tarantula.sql | 14 + db/migrations/0051_perpetual_war_machine.sql | 5 + db/migrations/0052_little_legion.sql | 2 + db/migrations/0053_fantastic_synch.sql | 1 + db/migrations/0054_polite_darwin.sql | 81 + db/migrations/0055_lonely_excalibur.sql | 69 + db/migrations/0056_majestic_khan.sql | 34 + db/migrations/0057_fancy_serpent_society.sql | 29 + db/migrations/0058_familiar_bloodscream.sql | 1 + db/migrations/0059_familiar_yellow_claw.sql | 1 + db/migrations/0060_confused_ultimo.sql | 32 + db/migrations/0061_warm_secret_warriors.sql | 2 + db/migrations/0062_zippy_human_torch.sql | 12 + db/migrations/0063_amused_nightcrawler.sql | 1 + db/migrations/0064_curved_tony_stark.sql | 1 + db/migrations/0065_redundant_sunset_bain.sql | 9 + db/migrations/0066_powerful_shard.sql | 4 + db/migrations/0067_careless_chameleon.sql | 2 + db/migrations/0068_worried_annihilus.sql | 2 + db/migrations/0069_whole_madelyne_pryor.sql | 2 + db/migrations/0070_cool_rage.sql | 2 + db/migrations/0071_worthless_shadow_king.sql | 1 + db/migrations/0072_certain_punisher.sql | 1 + db/migrations/0073_damp_reaper.sql | 13 + db/migrations/0074_wet_ezekiel.sql | 2 + db/migrations/0075_tough_epoch.sql | 46 + db/migrations/0076_cloudy_vindicator.sql | 1 + db/migrations/0077_past_ted_forrester.sql | 1 + db/migrations/0078_empty_shooting_star.sql | 26 + db/migrations/0079_big_sue_storm.sql | 10 + db/migrations/0080_slim_quasar.sql | 4 + db/migrations/0081_wealthy_roxanne_simpson.sql | 4 + db/migrations/0082_serious_revanche.sql | 40 + db/migrations/0083_dark_firestar.sql | 3 + db/migrations/0084_familiar_skrulls.sql | 2 + db/migrations/0085_thankful_medusa.sql | 6 + db/migrations/0086_chubby_proudstar.sql | 18 + db/migrations/0087_tidy_eddie_brock.sql | 59 + db/migrations/0088_secret_quentin_quire.sql | 13 + db/migrations/0089_faulty_lester.sql | 6 + db/migrations/0090_outstanding_sebastian_shaw.sql | 7 + db/migrations/0091_condemned_warstar.sql | 2 + db/migrations/0092_smart_karma.sql | 2 + db/migrations/0093_young_the_hunter.sql | 1 + db/migrations/0094_yellow_living_tribunal.sql | 4 + db/migrations/0095_mute_lizard.sql | 1 + db/migrations/0096_far_lord_tyger.sql | 2 + db/migrations/meta/0000_snapshot.json | 360 + db/migrations/meta/0001_snapshot.json | 499 + db/migrations/meta/0002_snapshot.json | 506 + db/migrations/meta/0003_snapshot.json | 513 + db/migrations/meta/0004_snapshot.json | 533 + db/migrations/meta/0005_snapshot.json | 592 + db/migrations/meta/0006_snapshot.json | 541 + db/migrations/meta/0007_snapshot.json | 772 ++ db/migrations/meta/0008_snapshot.json | 772 ++ db/migrations/meta/0009_snapshot.json | 778 ++ db/migrations/meta/0010_snapshot.json | 1442 +++ db/migrations/meta/0011_snapshot.json | 1456 +++ db/migrations/meta/0012_snapshot.json | 1456 +++ db/migrations/meta/0013_snapshot.json | 1462 +++ db/migrations/meta/0014_snapshot.json | 1527 +++ db/migrations/meta/0015_snapshot.json | 1535 +++ db/migrations/meta/0016_snapshot.json | 1554 +++ db/migrations/meta/0017_snapshot.json | 1561 +++ db/migrations/meta/0018_snapshot.json | 1577 +++ db/migrations/meta/0019_snapshot.json | 1585 +++ db/migrations/meta/0020_snapshot.json | 1585 +++ db/migrations/meta/0021_snapshot.json | 1591 +++ db/migrations/meta/0022_snapshot.json | 1642 +++ db/migrations/meta/0023_snapshot.json | 2046 +++ db/migrations/meta/0024_snapshot.json | 2062 +++ db/migrations/meta/0025_snapshot.json | 2055 +++ db/migrations/meta/0026_snapshot.json | 2074 +++ db/migrations/meta/0027_snapshot.json | 2080 +++ db/migrations/meta/0028_snapshot.json | 2100 +++ db/migrations/meta/0029_snapshot.json | 2119 +++ db/migrations/meta/0030_snapshot.json | 2215 ++++ db/migrations/meta/0031_snapshot.json | 2221 ++++ db/migrations/meta/0032_snapshot.json | 2227 ++++ db/migrations/meta/0033_snapshot.json | 2247 ++++ db/migrations/meta/0034_snapshot.json | 2394 ++++ db/migrations/meta/0035_snapshot.json | 2388 ++++ db/migrations/meta/0036_snapshot.json | 2444 ++++ db/migrations/meta/0037_snapshot.json | 2501 ++++ db/migrations/meta/0038_snapshot.json | 2501 ++++ db/migrations/meta/0039_snapshot.json | 2529 ++++ db/migrations/meta/0040_snapshot.json | 2557 ++++ db/migrations/meta/0041_snapshot.json | 2566 ++++ db/migrations/meta/0042_snapshot.json | 2640 ++++ db/migrations/meta/0043_snapshot.json | 2620 ++++ db/migrations/meta/0044_snapshot.json | 2640 ++++ db/migrations/meta/0045_snapshot.json | 2651 ++++ db/migrations/meta/0046_snapshot.json | 2639 ++++ db/migrations/meta/0047_snapshot.json | 2892 +++++ db/migrations/meta/0048_snapshot.json | 2971 +++++ db/migrations/meta/0049_snapshot.json | 2979 +++++ db/migrations/meta/0050_snapshot.json | 3055 +++++ db/migrations/meta/0051_snapshot.json | 3086 +++++ db/migrations/meta/0052_snapshot.json | 3100 +++++ db/migrations/meta/0053_snapshot.json | 3107 +++++ db/migrations/meta/0054_snapshot.json | 3193 +++++ db/migrations/meta/0055_snapshot.json | 3145 +++++ db/migrations/meta/0056_snapshot.json | 3214 +++++ db/migrations/meta/0057_snapshot.json | 3214 +++++ db/migrations/meta/0058_snapshot.json | 3236 +++++ db/migrations/meta/0059_snapshot.json | 3264 +++++ db/migrations/meta/0060_snapshot.json | 3461 +++++ db/migrations/meta/0061_snapshot.json | 3475 +++++ db/migrations/meta/0062_snapshot.json | 3544 +++++ db/migrations/meta/0063_snapshot.json | 3538 +++++ db/migrations/meta/0064_snapshot.json | 3551 +++++ db/migrations/meta/0065_snapshot.json | 3609 ++++++ db/migrations/meta/0066_snapshot.json | 3609 ++++++ db/migrations/meta/0067_snapshot.json | 3656 ++++++ db/migrations/meta/0068_snapshot.json | 3656 ++++++ db/migrations/meta/0069_snapshot.json | 3656 ++++++ db/migrations/meta/0070_snapshot.json | 3656 ++++++ db/migrations/meta/0071_snapshot.json | 3656 ++++++ db/migrations/meta/0072_snapshot.json | 3643 ++++++ db/migrations/meta/0073_snapshot.json | 3676 ++++++ db/migrations/meta/0074_snapshot.json | 3676 ++++++ db/migrations/meta/0075_snapshot.json | 3978 ++++++ db/migrations/meta/0076_snapshot.json | 4167 ++++++ db/migrations/meta/0077_snapshot.json | 4175 ++++++ db/migrations/meta/0078_snapshot.json | 4175 ++++++ db/migrations/meta/0079_snapshot.json | 4175 ++++++ db/migrations/meta/0080_snapshot.json | 4175 ++++++ db/migrations/meta/0081_snapshot.json | 4175 ++++++ db/migrations/meta/0082_snapshot.json | 4453 +++++++ db/migrations/meta/0083_snapshot.json | 4453 +++++++ db/migrations/meta/0084_snapshot.json | 4453 +++++++ db/migrations/meta/0085_snapshot.json | 4453 +++++++ db/migrations/meta/0086_snapshot.json | 4557 +++++++ db/migrations/meta/0087_snapshot.json | 4971 +++++++ db/migrations/meta/0088_snapshot.json | 4743 +++++++ db/migrations/meta/0089_snapshot.json | 4743 +++++++ db/migrations/meta/0090_snapshot.json | 4627 +++++++ db/migrations/meta/0091_snapshot.json | 4627 +++++++ db/migrations/meta/0092_snapshot.json | 4627 +++++++ db/migrations/meta/0093_snapshot.json | 4635 +++++++ db/migrations/meta/0094_snapshot.json | 4641 +++++++ db/migrations/meta/0095_snapshot.json | 4654 +++++++ db/migrations/meta/0096_snapshot.json | 4673 +++++++ db/migrations/meta/_journal.json | 685 + db/schema/companies.ts | 13 + db/schema/contract.ts | 227 + db/schema/items.ts | 12 + db/schema/pq.ts | 67 + db/schema/projects.ts | 13 + db/schema/rfq.ts | 743 ++ db/schema/tasks.ts | 46 + db/schema/users.ts | 147 + db/schema/vendorData.ts | 205 + db/schema/vendorDocu.ts | 285 + db/schema/vendors.ts | 118 + db/seeds/companies.ts | 21 + db/seeds/companyseed.ts | 35 + db/seeds/contract.ts | 197 + db/seeds/itmeSeed.ts | 40 + db/seeds/projects.ts | 43 + db/seeds/rfqSeed.ts | 147 + db/seeds/seedDcoments.ts | 57 + db/seeds/seedIssueStage.ts | 76 + db/seeds/seedRevision.ts | 173 + db/seeds/seedRevisionSHI.ts | 162 + db/seeds/tasks.ts | 21 + db/seeds/taskseed.ts | 43 + db/seeds/users.ts | 21 + db/seeds/userseed.ts | 36 + db/seeds/vendorDocu.ts | 144 + db/seeds/vendorSeed.ts | 86 + db/seeds_2/companySeed.ts | 38 + db/seeds_2/itmeSeed.ts | 108 + db/seeds_2/rfqSeed.ts | 148 + db/seeds_2/seed.ts | 56 + db/seeds_2/taskSeed.ts | 46 + db/seeds_2/userSeed.ts | 39 + db/seeds_2/vendorSeed.ts | 105 + db/utils.ts | 286 + drizzle.config.ts | 11 + env.js | 49 + eslint.config.mjs | 16 + hooks/use-callback-ref.ts | 27 + hooks/use-data-table.ts | 334 + hooks/use-debounce.ts | 15 + hooks/use-debounced-callback.ts | 29 + hooks/use-form-schema.ts | 113 + hooks/use-media-query.ts | 19 + hooks/use-meta-color.ts | 25 + hooks/use-mobile.tsx | 19 + hooks/use-query-string.ts | 24 + hooks/use-toast.ts | 194 + hooks/useAutoSizeColumns.ts | 86 + i18n/client.ts | 67 + i18n/index.ts | 34 + i18n/locales/en/login.json | 43 + i18n/locales/en/translation.json | 75 + i18n/locales/ko/login.json | 41 + i18n/locales/ko/translation.json | 74 + i18n/settings.ts | 18 + lib/admin-users/repository.ts | 171 + lib/admin-users/service.ts | 531 + lib/admin-users/table/add-ausers-dialog.tsx | 348 + lib/admin-users/table/ausers-table-columns.tsx | 228 + .../table/ausers-table-floating-bar.tsx | 389 + .../table/ausers-table-toolbar-actions.tsx | 118 + lib/admin-users/table/ausers-table.tsx | 180 + lib/admin-users/table/delete-ausers-dialog.tsx | 149 + lib/admin-users/table/update-auser-sheet.tsx | 225 + lib/admin-users/validations.ts | 65 + lib/compose-refs.ts | 38 + lib/constants.ts | 3 + lib/data-table.ts | 181 + lib/docuSign/docuSignFns.ts | 383 + lib/docuSign/jwtConfig/README.md | 54 + lib/docuSign/jwtConfig/jwtConfig.json | 6 + lib/docuSign/jwtConfig/private.key | 29 + lib/docuSign/types.ts | 37 + lib/downloadFile.ts | 81 + lib/equip-class/repository.ts | 45 + lib/equip-class/service.ts | 85 + lib/equip-class/table/equipClass-table-columns.tsx | 99 + .../table/equipClass-table-toolbar-actions.tsx | 53 + lib/equip-class/table/equipClass-table.tsx | 133 + lib/equip-class/table/feature-flags-provider.tsx | 108 + lib/equip-class/validation.ts | 34 + lib/export.ts | 198 + lib/export_all.ts | 251 + lib/filter-columns.ts | 193 + lib/fonts.ts | 5 + lib/form-list/repository.ts | 46 + lib/form-list/service.ts | 84 + lib/form-list/table/feature-flags-provider.tsx | 108 + lib/form-list/table/formLists-table-columns.tsx | 132 + .../table/formLists-table-toolbar-actions.tsx | 53 + lib/form-list/table/formLists-table.tsx | 151 + lib/form-list/table/meta-sheet.tsx | 245 + lib/form-list/validation.ts | 36 + lib/forms/services.ts | 646 + lib/handle-error.ts | 22 + lib/id.ts | 43 + lib/items/repository.ts | 125 + lib/items/service.ts | 201 + lib/items/table/add-items-dialog.tsx | 156 + lib/items/table/delete-items-dialog.tsx | 149 + lib/items/table/feature-flags-provider.tsx | 108 + lib/items/table/feature-flags.tsx | 96 + lib/items/table/items-table-columns.tsx | 183 + lib/items/table/items-table-toolbar-actions.tsx | 67 + lib/items/table/items-table.tsx | 139 + lib/items/table/update-item-sheet.tsx | 178 + lib/items/validations.ts | 47 + lib/logger.ts | 26 + lib/mail/mailer.ts | 31 + lib/mail/sendEmail.ts | 36 + lib/mail/templates/admin-created.hbs | 78 + lib/mail/templates/admin-email-changed.hbs | 90 + lib/mail/templates/otp.hbs | 77 + lib/mail/templates/rfq-invite.hbs | 116 + lib/mail/templates/vendor-active.hbs | 51 + lib/mail/templates/vendor-pq-comment.hbs | 128 + lib/mail/templates/vendor-pq-status.hbs | 48 + lib/parsers.ts | 94 + lib/po/repository.ts | 44 + lib/po/service.ts | 431 + lib/po/service_r1.ts | 282 + lib/po/table/feature-flags-provider.tsx | 108 + lib/po/table/po-table-columns.tsx | 155 + lib/po/table/po-table-toolbar-actions.tsx | 53 + lib/po/table/po-table.tsx | 164 + lib/po/table/sign-request-dialog.tsx | 410 + lib/po/validations.ts | 67 + lib/pq/pq-review-table/feature-flags-provider.tsx | 108 + lib/pq/pq-review-table/vendors-table-columns.tsx | 212 + .../vendors-table-toolbar-actions.tsx | 41 + lib/pq/pq-review-table/vendors-table.tsx | 97 + lib/pq/repository.ts | 44 + lib/pq/service.ts | 987 ++ lib/pq/table/add-pq-dialog.tsx | 299 + lib/pq/table/delete-pqs-dialog.tsx | 149 + lib/pq/table/pq-table-column.tsx | 185 + lib/pq/table/pq-table-toolbar-actions.tsx | 55 + lib/pq/table/pq-table.tsx | 125 + lib/pq/table/update-pq-sheet.tsx | 272 + lib/pq/validations.ts | 36 + lib/rfqs/cbe-table/cbe-table-columns.tsx | 227 + lib/rfqs/cbe-table/cbe-table.tsx | 165 + lib/rfqs/cbe-table/feature-flags-provider.tsx | 108 + lib/rfqs/repository.ts | 232 + lib/rfqs/service.ts | 2810 ++++ lib/rfqs/table/BudgetaryRfqSelector.tsx | 261 + lib/rfqs/table/ItemsDialog.tsx | 744 ++ lib/rfqs/table/add-rfq-dialog.tsx | 349 + lib/rfqs/table/attachment-rfq-sheet.tsx | 430 + lib/rfqs/table/delete-rfqs-dialog.tsx | 149 + lib/rfqs/table/feature-flags-provider.tsx | 108 + lib/rfqs/table/feature-flags.tsx | 96 + lib/rfqs/table/rfqs-table-columns.tsx | 315 + lib/rfqs/table/rfqs-table-floating-bar.tsx | 338 + lib/rfqs/table/rfqs-table-toolbar-actions.tsx | 55 + lib/rfqs/table/rfqs-table.tsx | 264 + lib/rfqs/table/update-rfq-sheet.tsx | 283 + lib/rfqs/tbe-table/comments-sheet.tsx | 334 + lib/rfqs/tbe-table/feature-flags-provider.tsx | 108 + lib/rfqs/tbe-table/file-dialog.tsx | 141 + lib/rfqs/tbe-table/invite-vendors-dialog.tsx | 203 + lib/rfqs/tbe-table/tbe-table-columns.tsx | 300 + lib/rfqs/tbe-table/tbe-table-toolbar-actions.tsx | 60 + lib/rfqs/tbe-table/tbe-table.tsx | 190 + lib/rfqs/validations.ts | 274 + lib/rfqs/vendor-table/add-vendor-dialog.tsx | 37 + lib/rfqs/vendor-table/comments-sheet.tsx | 320 + lib/rfqs/vendor-table/feature-flags-provider.tsx | 108 + lib/rfqs/vendor-table/invite-vendors-dialog.tsx | 177 + .../vendor-list/vendor-list-table-column.tsx | 154 + .../vendor-table/vendor-list/vendor-list-table.tsx | 142 + lib/rfqs/vendor-table/vendors-table-columns.tsx | 276 + .../vendor-table/vendors-table-floating-bar.tsx | 137 + .../vendor-table/vendors-table-toolbar-actions.tsx | 84 + lib/rfqs/vendor-table/vendors-table.tsx | 210 + lib/roles/repository.ts | 94 + lib/roles/services.ts | 300 + lib/roles/table/add-role-dialog.tsx | 308 + lib/roles/table/assign-roles-sheet.tsx | 87 + lib/roles/table/delete-roles-dialog.tsx | 149 + lib/roles/table/role-table-toolbar-actions.tsx | 101 + lib/roles/table/roles-table-columns.tsx | 223 + lib/roles/table/roles-table.tsx | 169 + lib/roles/table/update-roles-sheet.tsx | 331 + .../userTable/assginedUsers-table-columns.tsx | 164 + lib/roles/userTable/assignedUsers-table.tsx | 159 + lib/roles/validations.ts | 80 + lib/storage.ts | 44 + lib/tag-numbering/repository.ts | 45 + lib/tag-numbering/service.ts | 123 + lib/tag-numbering/table/feature-flags-provider.tsx | 108 + lib/tag-numbering/table/meta-sheet.tsx | 226 + .../table/tagNumbering-table-columns.tsx | 131 + .../table/tagNumbering-table-toolbar-actions.tsx | 53 + lib/tag-numbering/table/tagNumbering-table.tsx | 151 + lib/tag-numbering/validation.ts | 39 + lib/tags/form-mapping-service.ts | 65 + lib/tags/repository.ts | 71 + lib/tags/service.ts | 796 ++ lib/tags/table/add-tag-dialog copy.tsx | 637 + lib/tags/table/add-tag-dialog.tsx | 893 ++ lib/tags/table/delete-tags-dialog.tsx | 151 + lib/tags/table/feature-flags-provider.tsx | 108 + lib/tags/table/tag-table-column.tsx | 164 + lib/tags/table/tag-table.tsx | 141 + lib/tags/table/tags-export.tsx | 155 + lib/tags/table/tags-table-floating-bar.tsx | 220 + lib/tags/table/tags-table-toolbar-actions.tsx | 598 + lib/tags/table/update-tag-sheet.tsx | 548 + lib/tags/validations.ts | 68 + lib/tasks/repository.ts | 166 + lib/tasks/service.ts | 561 + lib/tasks/table/add-tasks-dialog.tsx | 227 + lib/tasks/table/delete-tasks-dialog.tsx | 149 + lib/tasks/table/feature-flags-provider.tsx | 108 + lib/tasks/table/feature-flags.tsx | 96 + lib/tasks/table/tasks-table-columns.tsx | 262 + lib/tasks/table/tasks-table-floating-bar.tsx | 354 + lib/tasks/table/tasks-table-toolbar-actions.tsx | 117 + lib/tasks/table/tasks-table.tsx | 197 + lib/tasks/table/update-task-sheet.tsx | 230 + lib/tasks/utils.ts | 80 + lib/tasks/validations.ts | 50 + lib/tbe/service.ts | 0 lib/tbe/table/comments-sheet.tsx | 334 + lib/tbe/table/feature-flags-provider.tsx | 108 + lib/tbe/table/file-dialog.tsx | 141 + lib/tbe/table/invite-vendors-dialog.tsx | 203 + lib/tbe/table/tbe-table-columns.tsx | 249 + lib/tbe/table/tbe-table-toolbar-actions.tsx | 60 + lib/tbe/table/tbe-table.tsx | 204 + lib/unstable-cache.ts | 19 + lib/users/repository.ts | 128 + lib/users/send-otp.ts | 71 + lib/users/service.ts | 413 + lib/users/table/assign-roles-dialog.tsx | 194 + lib/users/table/users-table-columns.tsx | 154 + lib/users/table/users-table-toolbar-actions.tsx | 61 + lib/users/table/users-table.tsx | 150 + lib/users/verifyOtp.ts | 28 + lib/users/verifyToken.ts | 38 + lib/utils.ts | 75 + lib/vendor-data/services.ts | 99 + lib/vendor-document-list/repository.ts | 44 + lib/vendor-document-list/service.ts | 284 + lib/vendor-document-list/table/add-doc-dialog.tsx | 299 + .../table/delete-docs-dialog.tsx | 231 + .../table/doc-table-column.tsx | 202 + .../table/doc-table-toolbar-actions.tsx | 66 + lib/vendor-document-list/table/doc-table.tsx | 110 + .../table/update-\bdoc-sheet.tsx" | 267 + lib/vendor-document-list/validations.ts | 33 + lib/vendor-document/repository.ts | 44 + lib/vendor-document/service.ts | 346 + lib/vendor-document/table/doc-table-column.tsx | 150 + .../table/doc-table-toolbar-actions.tsx | 57 + lib/vendor-document/table/doc-table.tsx | 124 + lib/vendor-document/validations.ts | 33 + lib/vendor-rfq-response/service.ts | 301 + lib/vendor-rfq-response/types.ts | 76 + .../vendor-rfq-table/ItemsDialog.tsx | 125 + .../vendor-rfq-table/attachment-rfq-sheet.tsx | 106 + .../vendor-rfq-table/comments-sheet.tsx | 320 + .../vendor-rfq-table/feature-flags-provider.tsx | 108 + .../vendor-rfq-table/rfqs-table-columns.tsx | 421 + .../rfqs-table-toolbar-actions.tsx | 40 + .../vendor-rfq-table/rfqs-table.tsx | 280 + .../vendor-tbe-table/comments-sheet.tsx | 334 + .../vendor-tbe-table/feature-flags-provider.tsx | 108 + .../vendor-tbe-table/tbe-table-columns.tsx | 317 + .../vendor-tbe-table/tbe-table.tsx | 162 + .../vendor-tbe-table/tbeFileHandler.tsx | 355 + lib/vendors/contacts-table/add-contact-dialog.tsx | 175 + .../contacts-table/contact-table-columns.tsx | 195 + .../contact-table-toolbar-actions.tsx | 106 + lib/vendors/contacts-table/contact-table.tsx | 87 + .../contacts-table/feature-flags-provider.tsx | 108 + lib/vendors/items-table/add-item-dialog.tsx | 289 + lib/vendors/items-table/feature-flags-provider.tsx | 108 + lib/vendors/items-table/item-table-columns.tsx | 197 + .../items-table/item-table-toolbar-actions.tsx | 106 + lib/vendors/items-table/item-table.tsx | 85 + lib/vendors/repository.ts | 282 + .../rfq-history-table/feature-flags-provider.tsx | 108 + .../rfq-history-table-columns.tsx | 223 + .../rfq-history-table-toolbar-actions.tsx | 136 + .../rfq-history-table/rfq-history-table.tsx | 156 + .../rfq-history-table/rfq-items-table-dialog.tsx | 98 + lib/vendors/service.ts | 1345 ++ lib/vendors/table/approve-vendor-dialog.tsx | 150 + lib/vendors/table/attachmentButton.tsx | 69 + lib/vendors/table/feature-flags-provider.tsx | 108 + lib/vendors/table/request-vendor-pg-dialog.tsx | 150 + lib/vendors/table/send-vendor-dialog.tsx | 150 + lib/vendors/table/update-vendor-sheet.tsx | 270 + lib/vendors/table/vendors-table-columns.tsx | 279 + lib/vendors/table/vendors-table-floating-bar.tsx | 241 + .../table/vendors-table-toolbar-actions.tsx | 97 + lib/vendors/table/vendors-table.tsx | 121 + lib/vendors/validations.ts | 341 + middleware.ts | 84 + next-i18next.config.js | 7 + next.config.ts | 15 + package-lock.json | 12977 +++++++++++++++++++ package.json | 126 + pages/api/po/createContractFile.ts | 57 + pages/api/po/sendDocuSign.ts | 49 + pages/api/po/webhook.ts | 337 + postcss.config.mjs | 8 + public/file.svg | 1 + public/globals.css | 168 + public/globe.svg | 1 + public/images/02.jpg | Bin 0 -> 472844 bytes public/images/next.svg | 1 + public/images/placeholder.svg | 2065 +++ public/images/portalBG.png | Bin 0 -> 763154 bytes public/images/vercel.svg | 1 + public/locales/en/login.json | 23 + public/locales/en/translation.json | 0 public/locales/ko/login.json | 23 + public/locales/ko/translation.json | 0 public/next.svg | 1 + public/window.svg | 1 + statement-breakpoint | 0 tailwind.config.ts | 94 + tsconfig.json | 27 + types/react-file-pond.d.ts | 7 + types/table.d.ts | 78 + types/user.d.ts | 28 + types/vendorData.d.ts | 9 + 758 files changed, 373037 insertions(+) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/[lng]/evcp/bqtbe/page.tsx create mode 100644 app/[lng]/evcp/budgetary/[id]/cbe/page.tsx create mode 100644 app/[lng]/evcp/budgetary/[id]/layout.tsx create mode 100644 app/[lng]/evcp/budgetary/[id]/page.tsx create mode 100644 app/[lng]/evcp/budgetary/[id]/tbe/page.tsx create mode 100644 app/[lng]/evcp/budgetary/page.tsx create mode 100644 app/[lng]/evcp/equip-class/page.tsx create mode 100644 app/[lng]/evcp/faq/manage/actions.ts create mode 100644 app/[lng]/evcp/faq/manage/page.tsx create mode 100644 app/[lng]/evcp/faq/page.tsx create mode 100644 app/[lng]/evcp/form-list/page.tsx create mode 100644 app/[lng]/evcp/items/page.tsx create mode 100644 app/[lng]/evcp/layout.tsx create mode 100644 app/[lng]/evcp/page.tsx create mode 100644 app/[lng]/evcp/po/page.tsx create mode 100644 app/[lng]/evcp/pq-criteria/page.tsx create mode 100644 app/[lng]/evcp/pq/[vendorId]/page.tsx create mode 100644 app/[lng]/evcp/pq/page.tsx create mode 100644 app/[lng]/evcp/report/page.tsx create mode 100644 app/[lng]/evcp/rfq/[id]/cbe/page.tsx create mode 100644 app/[lng]/evcp/rfq/[id]/layout.tsx create mode 100644 app/[lng]/evcp/rfq/[id]/page.tsx create mode 100644 app/[lng]/evcp/rfq/[id]/tbe/page.tsx create mode 100644 app/[lng]/evcp/rfq/page.tsx create mode 100644 app/[lng]/evcp/settings/layout.tsx create mode 100644 app/[lng]/evcp/settings/page.tsx create mode 100644 app/[lng]/evcp/settings/preferences/page.tsx create mode 100644 app/[lng]/evcp/system/admin-users/page.tsx create mode 100644 app/[lng]/evcp/system/layout.tsx create mode 100644 app/[lng]/evcp/system/page.tsx create mode 100644 app/[lng]/evcp/system/permissions/page.tsx create mode 100644 app/[lng]/evcp/system/roles/page.tsx create mode 100644 app/[lng]/evcp/tag-numbering/page.tsx create mode 100644 app/[lng]/evcp/tasks/page.tsx create mode 100644 app/[lng]/evcp/vendors/[id]/info/items/page.tsx create mode 100644 app/[lng]/evcp/vendors/[id]/info/layout.tsx create mode 100644 app/[lng]/evcp/vendors/[id]/info/page.tsx create mode 100644 app/[lng]/evcp/vendors/[id]/info/rfq-history/page.tsx create mode 100644 app/[lng]/evcp/vendors/page.tsx create mode 100644 app/[lng]/login/page.tsx create mode 100644 app/[lng]/partners/(partners)/dashboard/page.tsx create mode 100644 app/[lng]/partners/(partners)/document-list/[contractId]/page.tsx create mode 100644 app/[lng]/partners/(partners)/document-list/layout.tsx create mode 100644 app/[lng]/partners/(partners)/document-list/page.tsx create mode 100644 app/[lng]/partners/(partners)/documents/[contractId]/page.tsx create mode 100644 app/[lng]/partners/(partners)/documents/layout.tsx create mode 100644 app/[lng]/partners/(partners)/documents/page.tsx create mode 100644 app/[lng]/partners/(partners)/layout.tsx create mode 100644 app/[lng]/partners/(partners)/rfq/page.tsx create mode 100644 app/[lng]/partners/(partners)/system/page.tsx create mode 100644 app/[lng]/partners/(partners)/tbe/page.tsx create mode 100644 app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx create mode 100644 app/[lng]/partners/(partners)/vendor-data/layout.tsx create mode 100644 app/[lng]/partners/(partners)/vendor-data/page.tsx create mode 100644 app/[lng]/partners/(partners)/vendor-data/tag/[id]/page.tsx create mode 100644 app/[lng]/partners/page.tsx create mode 100644 app/[lng]/partners/pq/page.tsx create mode 100644 app/[lng]/partners/repository/page.tsx create mode 100644 app/[lng]/partners/signup/page.tsx create mode 100644 app/[lng]/qna/layout.tsx create mode 100644 app/[lng]/qna/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/files/[...path]/route.ts create mode 100644 app/api/rfq-download/route.ts create mode 100644 app/api/rfq-upload/route.ts create mode 100644 app/api/tbe-download/route.ts create mode 100644 app/api/upload/route.ts create mode 100644 app/api/vendors/attachments/download-temp/route.ts create mode 100644 app/api/vendors/erp/route.ts create mode 100644 app/favicon.ico create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 atoms.ts create mode 100644 components.json create mode 100644 components/ProjectSelector.tsx create mode 100644 components/client-data-table/data-table-column-simple-header.tsx create mode 100644 components/client-data-table/data-table-filter-list.tsx create mode 100644 components/client-data-table/data-table-group-list.tsx create mode 100644 components/client-data-table/data-table-pagination.tsx create mode 100644 components/client-data-table/data-table-resizer.tsx create mode 100644 components/client-data-table/data-table-sort-list.tsx create mode 100644 components/client-data-table/data-table-toolbar.tsx create mode 100644 components/client-data-table/data-table-view-options.tsx create mode 100644 components/client-data-table/data-table.tsx create mode 100644 components/data-table/data-table-advanced-toolbar.tsx create mode 100644 components/data-table/data-table-column-header.tsx create mode 100644 components/data-table/data-table-column-resizable.tsx create mode 100644 components/data-table/data-table-column-simple-header.tsx create mode 100644 components/data-table/data-table-faceted-filter.tsx create mode 100644 components/data-table/data-table-filter-list.tsx create mode 100644 components/data-table/data-table-grobal-filter.tsx create mode 100644 components/data-table/data-table-group-list.tsx create mode 100644 components/data-table/data-table-pagination.tsx create mode 100644 components/data-table/data-table-pin-left.tsx create mode 100644 components/data-table/data-table-pin-right.tsx create mode 100644 components/data-table/data-table-pin.tsx create mode 100644 components/data-table/data-table-resizer.tsx create mode 100644 components/data-table/data-table-skeleton.tsx create mode 100644 components/data-table/data-table-sort-list.tsx create mode 100644 components/data-table/data-table-toolbar.tsx create mode 100644 components/data-table/data-table-view-options.tsx create mode 100644 components/data-table/data-table.tsx create mode 100644 components/date-range-picker.tsx create mode 100644 components/document-lists/vendor-doc-list-client.tsx create mode 100644 components/documents/RevisionForm.tsx create mode 100644 components/documents/StageList.tsx create mode 100644 components/documents/StageListfromSHI.tsx create mode 100644 components/documents/add-document-dialog.tsx create mode 100644 components/documents/document-container.tsx create mode 100644 components/documents/project-swicher.tsx create mode 100644 components/documents/vendor-docs.client.tsx create mode 100644 components/documents/view-document-dialog.tsx create mode 100644 components/faq/FaqCard.tsx create mode 100644 components/faq/FaqManager.tsx create mode 100644 components/form-data/form-data-table-columns.tsx create mode 100644 components/form-data/form-data-table.tsx create mode 100644 components/form-data/update-form-sheet.tsx create mode 100644 components/kbd.tsx create mode 100644 components/layout/Footer.tsx create mode 100644 components/layout/Header.tsx create mode 100644 components/layout/MobileMenu.tsx create mode 100644 components/layout/command-menu.tsx create mode 100644 components/layout/createEmotionCashe.ts create mode 100644 components/layout/mode-switcher.tsx create mode 100644 components/layout/providers.tsx create mode 100644 components/layout/sidebar-nav.tsx create mode 100644 components/login/login-form-skeleton.tsx create mode 100644 components/login/login-form.tsx create mode 100644 components/login/partner-auth-form.tsx create mode 100644 components/pq/pq-input-tabs.tsx create mode 100644 components/pq/pq-review-detail.tsx create mode 100644 components/pq/pq-review-table.tsx create mode 100644 components/settings/account-form.tsx create mode 100644 components/settings/appearance-form.tsx create mode 100644 components/shell.tsx create mode 100644 components/signup/join-form-skeleton.tsx create mode 100644 components/signup/join-form.tsx create mode 100644 components/system/permissionDialog.tsx create mode 100644 components/system/permissionsTree.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/action-dialog.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/dropzone-primitive.tsx create mode 100644 components/ui/dropzone.tsx create mode 100644 components/ui/faceted-filter.tsx create mode 100644 components/ui/file-list.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/multi-select.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/portal.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/sortable.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/toasterSonner.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/vendor-data/project-swicher.tsx create mode 100644 components/vendor-data/sidebar.tsx create mode 100644 components/vendor-data/tag-table/add-tag-dialog.tsx create mode 100644 components/vendor-data/tag-table/tag-table-column.tsx create mode 100644 components/vendor-data/tag-table/tag-table.tsx create mode 100644 components/vendor-data/tag-table/tag-type-definitions.ts create mode 100644 components/vendor-data/vendor-data-container.tsx create mode 100644 config/data-table.ts create mode 100644 config/equipClassColumnsConfig.ts create mode 100644 config/euserColumnsConfig.ts create mode 100644 config/faqDataConfig.ts create mode 100644 config/formListsColumnsConfig.ts create mode 100644 config/itemsColumnsConfig.ts create mode 100644 config/language.ts create mode 100644 config/menuConfig.ts create mode 100644 config/permissionsConfig.ts create mode 100644 config/poColumnsConfig.ts create mode 100644 config/rfqHistoryColumnsConfig.ts create mode 100644 config/rfqsColumnsConfig.ts create mode 100644 config/rfqsVendorColumnsConfig.ts create mode 100644 config/roleColumnsConfig.ts create mode 100644 config/site.ts create mode 100644 config/tagNumberingColumnsConfig.ts create mode 100644 config/tasksColumnsConfig.ts create mode 100644 config/userColumnsConfig.ts create mode 100644 config/vendorCbeColumnsConfig.ts create mode 100644 config/vendorColumnsConfig.ts create mode 100644 config/vendorContactsColumnsConfig.ts create mode 100644 config/vendorItemsColumnsConfig.ts create mode 100644 config/vendorRfbColumnsConfig.ts create mode 100644 config/vendorTbeColumnsConfig.ts create mode 100644 db/db.ts create mode 100644 db/migrations/0000_dusty_skaar.sql create mode 100644 db/migrations/0001_robust_landau.sql create mode 100644 db/migrations/0002_puzzling_johnny_blaze.sql create mode 100644 db/migrations/0003_unknown_speed_demon.sql create mode 100644 db/migrations/0004_cheerful_stark_industries.sql create mode 100644 db/migrations/0005_loud_whizzer.sql create mode 100644 db/migrations/0006_tan_mongu.sql create mode 100644 db/migrations/0007_big_franklin_storm.sql create mode 100644 db/migrations/0008_previous_omega_red.sql create mode 100644 db/migrations/0009_aromatic_falcon.sql create mode 100644 db/migrations/0010_big_sue_storm.sql create mode 100644 db/migrations/0011_tense_madame_web.sql create mode 100644 db/migrations/0012_giant_sunfire.sql create mode 100644 db/migrations/0013_exotic_blazing_skull.sql create mode 100644 db/migrations/0014_clammy_power_man.sql create mode 100644 db/migrations/0015_black_fixer.sql create mode 100644 db/migrations/0016_fat_komodo.sql create mode 100644 db/migrations/0017_thin_chimera.sql create mode 100644 db/migrations/0018_tiny_robbie_robertson.sql create mode 100644 db/migrations/0019_bumpy_anthem.sql create mode 100644 db/migrations/0020_chubby_carnage.sql create mode 100644 db/migrations/0021_nappy_dexter_bennett.sql create mode 100644 db/migrations/0022_panoramic_mad_thinker.sql create mode 100644 db/migrations/0023_familiar_raza.sql create mode 100644 db/migrations/0024_eager_bill_hollister.sql create mode 100644 db/migrations/0025_complex_husk.sql create mode 100644 db/migrations/0026_tiresome_jackpot.sql create mode 100644 db/migrations/0027_yummy_senator_kelly.sql create mode 100644 db/migrations/0028_eminent_shockwave.sql create mode 100644 db/migrations/0029_oval_carmella_unuscione.sql create mode 100644 db/migrations/0030_zippy_the_order.sql create mode 100644 db/migrations/0031_curvy_starhawk.sql create mode 100644 db/migrations/0032_unusual_speedball.sql create mode 100644 db/migrations/0033_chemical_maggott.sql create mode 100644 db/migrations/0035_sweet_revanche.sql create mode 100644 db/migrations/0036_aberrant_marvel_apes.sql create mode 100644 db/migrations/0037_legal_pretty_boy.sql create mode 100644 db/migrations/0038_tough_mimic.sql create mode 100644 db/migrations/0039_elite_zemo.sql create mode 100644 db/migrations/0040_absurd_human_cannonball.sql create mode 100644 db/migrations/0041_absurd_spyke.sql create mode 100644 db/migrations/0042_panoramic_daimon_hellstrom.sql create mode 100644 db/migrations/0043_pale_black_bird.sql create mode 100644 db/migrations/0044_lush_tenebrous.sql create mode 100644 db/migrations/0045_complex_gamora.sql create mode 100644 db/migrations/0046_overjoyed_tinkerer.sql create mode 100644 db/migrations/0047_broad_the_executioner.sql create mode 100644 db/migrations/0048_strange_ultimo.sql create mode 100644 db/migrations/0049_green_mattie_franklin.sql create mode 100644 db/migrations/0050_furry_black_tarantula.sql create mode 100644 db/migrations/0051_perpetual_war_machine.sql create mode 100644 db/migrations/0052_little_legion.sql create mode 100644 db/migrations/0053_fantastic_synch.sql create mode 100644 db/migrations/0054_polite_darwin.sql create mode 100644 db/migrations/0055_lonely_excalibur.sql create mode 100644 db/migrations/0056_majestic_khan.sql create mode 100644 db/migrations/0057_fancy_serpent_society.sql create mode 100644 db/migrations/0058_familiar_bloodscream.sql create mode 100644 db/migrations/0059_familiar_yellow_claw.sql create mode 100644 db/migrations/0060_confused_ultimo.sql create mode 100644 db/migrations/0061_warm_secret_warriors.sql create mode 100644 db/migrations/0062_zippy_human_torch.sql create mode 100644 db/migrations/0063_amused_nightcrawler.sql create mode 100644 db/migrations/0064_curved_tony_stark.sql create mode 100644 db/migrations/0065_redundant_sunset_bain.sql create mode 100644 db/migrations/0066_powerful_shard.sql create mode 100644 db/migrations/0067_careless_chameleon.sql create mode 100644 db/migrations/0068_worried_annihilus.sql create mode 100644 db/migrations/0069_whole_madelyne_pryor.sql create mode 100644 db/migrations/0070_cool_rage.sql create mode 100644 db/migrations/0071_worthless_shadow_king.sql create mode 100644 db/migrations/0072_certain_punisher.sql create mode 100644 db/migrations/0073_damp_reaper.sql create mode 100644 db/migrations/0074_wet_ezekiel.sql create mode 100644 db/migrations/0075_tough_epoch.sql create mode 100644 db/migrations/0076_cloudy_vindicator.sql create mode 100644 db/migrations/0077_past_ted_forrester.sql create mode 100644 db/migrations/0078_empty_shooting_star.sql create mode 100644 db/migrations/0079_big_sue_storm.sql create mode 100644 db/migrations/0080_slim_quasar.sql create mode 100644 db/migrations/0081_wealthy_roxanne_simpson.sql create mode 100644 db/migrations/0082_serious_revanche.sql create mode 100644 db/migrations/0083_dark_firestar.sql create mode 100644 db/migrations/0084_familiar_skrulls.sql create mode 100644 db/migrations/0085_thankful_medusa.sql create mode 100644 db/migrations/0086_chubby_proudstar.sql create mode 100644 db/migrations/0087_tidy_eddie_brock.sql create mode 100644 db/migrations/0088_secret_quentin_quire.sql create mode 100644 db/migrations/0089_faulty_lester.sql create mode 100644 db/migrations/0090_outstanding_sebastian_shaw.sql create mode 100644 db/migrations/0091_condemned_warstar.sql create mode 100644 db/migrations/0092_smart_karma.sql create mode 100644 db/migrations/0093_young_the_hunter.sql create mode 100644 db/migrations/0094_yellow_living_tribunal.sql create mode 100644 db/migrations/0095_mute_lizard.sql create mode 100644 db/migrations/0096_far_lord_tyger.sql create mode 100644 db/migrations/meta/0000_snapshot.json create mode 100644 db/migrations/meta/0001_snapshot.json create mode 100644 db/migrations/meta/0002_snapshot.json create mode 100644 db/migrations/meta/0003_snapshot.json create mode 100644 db/migrations/meta/0004_snapshot.json create mode 100644 db/migrations/meta/0005_snapshot.json create mode 100644 db/migrations/meta/0006_snapshot.json create mode 100644 db/migrations/meta/0007_snapshot.json create mode 100644 db/migrations/meta/0008_snapshot.json create mode 100644 db/migrations/meta/0009_snapshot.json create mode 100644 db/migrations/meta/0010_snapshot.json create mode 100644 db/migrations/meta/0011_snapshot.json create mode 100644 db/migrations/meta/0012_snapshot.json create mode 100644 db/migrations/meta/0013_snapshot.json create mode 100644 db/migrations/meta/0014_snapshot.json create mode 100644 db/migrations/meta/0015_snapshot.json create mode 100644 db/migrations/meta/0016_snapshot.json create mode 100644 db/migrations/meta/0017_snapshot.json create mode 100644 db/migrations/meta/0018_snapshot.json create mode 100644 db/migrations/meta/0019_snapshot.json create mode 100644 db/migrations/meta/0020_snapshot.json create mode 100644 db/migrations/meta/0021_snapshot.json create mode 100644 db/migrations/meta/0022_snapshot.json create mode 100644 db/migrations/meta/0023_snapshot.json create mode 100644 db/migrations/meta/0024_snapshot.json create mode 100644 db/migrations/meta/0025_snapshot.json create mode 100644 db/migrations/meta/0026_snapshot.json create mode 100644 db/migrations/meta/0027_snapshot.json create mode 100644 db/migrations/meta/0028_snapshot.json create mode 100644 db/migrations/meta/0029_snapshot.json create mode 100644 db/migrations/meta/0030_snapshot.json create mode 100644 db/migrations/meta/0031_snapshot.json create mode 100644 db/migrations/meta/0032_snapshot.json create mode 100644 db/migrations/meta/0033_snapshot.json create mode 100644 db/migrations/meta/0034_snapshot.json create mode 100644 db/migrations/meta/0035_snapshot.json create mode 100644 db/migrations/meta/0036_snapshot.json create mode 100644 db/migrations/meta/0037_snapshot.json create mode 100644 db/migrations/meta/0038_snapshot.json create mode 100644 db/migrations/meta/0039_snapshot.json create mode 100644 db/migrations/meta/0040_snapshot.json create mode 100644 db/migrations/meta/0041_snapshot.json create mode 100644 db/migrations/meta/0042_snapshot.json create mode 100644 db/migrations/meta/0043_snapshot.json create mode 100644 db/migrations/meta/0044_snapshot.json create mode 100644 db/migrations/meta/0045_snapshot.json create mode 100644 db/migrations/meta/0046_snapshot.json create mode 100644 db/migrations/meta/0047_snapshot.json create mode 100644 db/migrations/meta/0048_snapshot.json create mode 100644 db/migrations/meta/0049_snapshot.json create mode 100644 db/migrations/meta/0050_snapshot.json create mode 100644 db/migrations/meta/0051_snapshot.json create mode 100644 db/migrations/meta/0052_snapshot.json create mode 100644 db/migrations/meta/0053_snapshot.json create mode 100644 db/migrations/meta/0054_snapshot.json create mode 100644 db/migrations/meta/0055_snapshot.json create mode 100644 db/migrations/meta/0056_snapshot.json create mode 100644 db/migrations/meta/0057_snapshot.json create mode 100644 db/migrations/meta/0058_snapshot.json create mode 100644 db/migrations/meta/0059_snapshot.json create mode 100644 db/migrations/meta/0060_snapshot.json create mode 100644 db/migrations/meta/0061_snapshot.json create mode 100644 db/migrations/meta/0062_snapshot.json create mode 100644 db/migrations/meta/0063_snapshot.json create mode 100644 db/migrations/meta/0064_snapshot.json create mode 100644 db/migrations/meta/0065_snapshot.json create mode 100644 db/migrations/meta/0066_snapshot.json create mode 100644 db/migrations/meta/0067_snapshot.json create mode 100644 db/migrations/meta/0068_snapshot.json create mode 100644 db/migrations/meta/0069_snapshot.json create mode 100644 db/migrations/meta/0070_snapshot.json create mode 100644 db/migrations/meta/0071_snapshot.json create mode 100644 db/migrations/meta/0072_snapshot.json create mode 100644 db/migrations/meta/0073_snapshot.json create mode 100644 db/migrations/meta/0074_snapshot.json create mode 100644 db/migrations/meta/0075_snapshot.json create mode 100644 db/migrations/meta/0076_snapshot.json create mode 100644 db/migrations/meta/0077_snapshot.json create mode 100644 db/migrations/meta/0078_snapshot.json create mode 100644 db/migrations/meta/0079_snapshot.json create mode 100644 db/migrations/meta/0080_snapshot.json create mode 100644 db/migrations/meta/0081_snapshot.json create mode 100644 db/migrations/meta/0082_snapshot.json create mode 100644 db/migrations/meta/0083_snapshot.json create mode 100644 db/migrations/meta/0084_snapshot.json create mode 100644 db/migrations/meta/0085_snapshot.json create mode 100644 db/migrations/meta/0086_snapshot.json create mode 100644 db/migrations/meta/0087_snapshot.json create mode 100644 db/migrations/meta/0088_snapshot.json create mode 100644 db/migrations/meta/0089_snapshot.json create mode 100644 db/migrations/meta/0090_snapshot.json create mode 100644 db/migrations/meta/0091_snapshot.json create mode 100644 db/migrations/meta/0092_snapshot.json create mode 100644 db/migrations/meta/0093_snapshot.json create mode 100644 db/migrations/meta/0094_snapshot.json create mode 100644 db/migrations/meta/0095_snapshot.json create mode 100644 db/migrations/meta/0096_snapshot.json create mode 100644 db/migrations/meta/_journal.json create mode 100644 db/schema/companies.ts create mode 100644 db/schema/contract.ts create mode 100644 db/schema/items.ts create mode 100644 db/schema/pq.ts create mode 100644 db/schema/projects.ts create mode 100644 db/schema/rfq.ts create mode 100644 db/schema/tasks.ts create mode 100644 db/schema/users.ts create mode 100644 db/schema/vendorData.ts create mode 100644 db/schema/vendorDocu.ts create mode 100644 db/schema/vendors.ts create mode 100644 db/seeds/companies.ts create mode 100644 db/seeds/companyseed.ts create mode 100644 db/seeds/contract.ts create mode 100644 db/seeds/itmeSeed.ts create mode 100644 db/seeds/projects.ts create mode 100644 db/seeds/rfqSeed.ts create mode 100644 db/seeds/seedDcoments.ts create mode 100644 db/seeds/seedIssueStage.ts create mode 100644 db/seeds/seedRevision.ts create mode 100644 db/seeds/seedRevisionSHI.ts create mode 100644 db/seeds/tasks.ts create mode 100644 db/seeds/taskseed.ts create mode 100644 db/seeds/users.ts create mode 100644 db/seeds/userseed.ts create mode 100644 db/seeds/vendorDocu.ts create mode 100644 db/seeds/vendorSeed.ts create mode 100644 db/seeds_2/companySeed.ts create mode 100644 db/seeds_2/itmeSeed.ts create mode 100644 db/seeds_2/rfqSeed.ts create mode 100644 db/seeds_2/seed.ts create mode 100644 db/seeds_2/taskSeed.ts create mode 100644 db/seeds_2/userSeed.ts create mode 100644 db/seeds_2/vendorSeed.ts create mode 100644 db/utils.ts create mode 100644 drizzle.config.ts create mode 100644 env.js create mode 100644 eslint.config.mjs create mode 100644 hooks/use-callback-ref.ts create mode 100644 hooks/use-data-table.ts create mode 100644 hooks/use-debounce.ts create mode 100644 hooks/use-debounced-callback.ts create mode 100644 hooks/use-form-schema.ts create mode 100644 hooks/use-media-query.ts create mode 100644 hooks/use-meta-color.ts create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-query-string.ts create mode 100644 hooks/use-toast.ts create mode 100644 hooks/useAutoSizeColumns.ts create mode 100644 i18n/client.ts create mode 100644 i18n/index.ts create mode 100644 i18n/locales/en/login.json create mode 100644 i18n/locales/en/translation.json create mode 100644 i18n/locales/ko/login.json create mode 100644 i18n/locales/ko/translation.json create mode 100644 i18n/settings.ts create mode 100644 lib/admin-users/repository.ts create mode 100644 lib/admin-users/service.ts create mode 100644 lib/admin-users/table/add-ausers-dialog.tsx create mode 100644 lib/admin-users/table/ausers-table-columns.tsx create mode 100644 lib/admin-users/table/ausers-table-floating-bar.tsx create mode 100644 lib/admin-users/table/ausers-table-toolbar-actions.tsx create mode 100644 lib/admin-users/table/ausers-table.tsx create mode 100644 lib/admin-users/table/delete-ausers-dialog.tsx create mode 100644 lib/admin-users/table/update-auser-sheet.tsx create mode 100644 lib/admin-users/validations.ts create mode 100644 lib/compose-refs.ts create mode 100644 lib/constants.ts create mode 100644 lib/data-table.ts create mode 100644 lib/docuSign/docuSignFns.ts create mode 100644 lib/docuSign/jwtConfig/README.md create mode 100644 lib/docuSign/jwtConfig/jwtConfig.json create mode 100644 lib/docuSign/jwtConfig/private.key create mode 100644 lib/docuSign/types.ts create mode 100644 lib/downloadFile.ts create mode 100644 lib/equip-class/repository.ts create mode 100644 lib/equip-class/service.ts create mode 100644 lib/equip-class/table/equipClass-table-columns.tsx create mode 100644 lib/equip-class/table/equipClass-table-toolbar-actions.tsx create mode 100644 lib/equip-class/table/equipClass-table.tsx create mode 100644 lib/equip-class/table/feature-flags-provider.tsx create mode 100644 lib/equip-class/validation.ts create mode 100644 lib/export.ts create mode 100644 lib/export_all.ts create mode 100644 lib/filter-columns.ts create mode 100644 lib/fonts.ts create mode 100644 lib/form-list/repository.ts create mode 100644 lib/form-list/service.ts create mode 100644 lib/form-list/table/feature-flags-provider.tsx create mode 100644 lib/form-list/table/formLists-table-columns.tsx create mode 100644 lib/form-list/table/formLists-table-toolbar-actions.tsx create mode 100644 lib/form-list/table/formLists-table.tsx create mode 100644 lib/form-list/table/meta-sheet.tsx create mode 100644 lib/form-list/validation.ts create mode 100644 lib/forms/services.ts create mode 100644 lib/handle-error.ts create mode 100644 lib/id.ts create mode 100644 lib/items/repository.ts create mode 100644 lib/items/service.ts create mode 100644 lib/items/table/add-items-dialog.tsx create mode 100644 lib/items/table/delete-items-dialog.tsx create mode 100644 lib/items/table/feature-flags-provider.tsx create mode 100644 lib/items/table/feature-flags.tsx create mode 100644 lib/items/table/items-table-columns.tsx create mode 100644 lib/items/table/items-table-toolbar-actions.tsx create mode 100644 lib/items/table/items-table.tsx create mode 100644 lib/items/table/update-item-sheet.tsx create mode 100644 lib/items/validations.ts create mode 100644 lib/logger.ts create mode 100644 lib/mail/mailer.ts create mode 100644 lib/mail/sendEmail.ts create mode 100644 lib/mail/templates/admin-created.hbs create mode 100644 lib/mail/templates/admin-email-changed.hbs create mode 100644 lib/mail/templates/otp.hbs create mode 100644 lib/mail/templates/rfq-invite.hbs create mode 100644 lib/mail/templates/vendor-active.hbs create mode 100644 lib/mail/templates/vendor-pq-comment.hbs create mode 100644 lib/mail/templates/vendor-pq-status.hbs create mode 100644 lib/parsers.ts create mode 100644 lib/po/repository.ts create mode 100644 lib/po/service.ts create mode 100644 lib/po/service_r1.ts create mode 100644 lib/po/table/feature-flags-provider.tsx create mode 100644 lib/po/table/po-table-columns.tsx create mode 100644 lib/po/table/po-table-toolbar-actions.tsx create mode 100644 lib/po/table/po-table.tsx create mode 100644 lib/po/table/sign-request-dialog.tsx create mode 100644 lib/po/validations.ts create mode 100644 lib/pq/pq-review-table/feature-flags-provider.tsx create mode 100644 lib/pq/pq-review-table/vendors-table-columns.tsx create mode 100644 lib/pq/pq-review-table/vendors-table-toolbar-actions.tsx create mode 100644 lib/pq/pq-review-table/vendors-table.tsx create mode 100644 lib/pq/repository.ts create mode 100644 lib/pq/service.ts create mode 100644 lib/pq/table/add-pq-dialog.tsx create mode 100644 lib/pq/table/delete-pqs-dialog.tsx create mode 100644 lib/pq/table/pq-table-column.tsx create mode 100644 lib/pq/table/pq-table-toolbar-actions.tsx create mode 100644 lib/pq/table/pq-table.tsx create mode 100644 lib/pq/table/update-pq-sheet.tsx create mode 100644 lib/pq/validations.ts create mode 100644 lib/rfqs/cbe-table/cbe-table-columns.tsx create mode 100644 lib/rfqs/cbe-table/cbe-table.tsx create mode 100644 lib/rfqs/cbe-table/feature-flags-provider.tsx create mode 100644 lib/rfqs/repository.ts create mode 100644 lib/rfqs/service.ts create mode 100644 lib/rfqs/table/BudgetaryRfqSelector.tsx create mode 100644 lib/rfqs/table/ItemsDialog.tsx create mode 100644 lib/rfqs/table/add-rfq-dialog.tsx create mode 100644 lib/rfqs/table/attachment-rfq-sheet.tsx create mode 100644 lib/rfqs/table/delete-rfqs-dialog.tsx create mode 100644 lib/rfqs/table/feature-flags-provider.tsx create mode 100644 lib/rfqs/table/feature-flags.tsx create mode 100644 lib/rfqs/table/rfqs-table-columns.tsx create mode 100644 lib/rfqs/table/rfqs-table-floating-bar.tsx create mode 100644 lib/rfqs/table/rfqs-table-toolbar-actions.tsx create mode 100644 lib/rfqs/table/rfqs-table.tsx create mode 100644 lib/rfqs/table/update-rfq-sheet.tsx create mode 100644 lib/rfqs/tbe-table/comments-sheet.tsx create mode 100644 lib/rfqs/tbe-table/feature-flags-provider.tsx create mode 100644 lib/rfqs/tbe-table/file-dialog.tsx create mode 100644 lib/rfqs/tbe-table/invite-vendors-dialog.tsx create mode 100644 lib/rfqs/tbe-table/tbe-table-columns.tsx create mode 100644 lib/rfqs/tbe-table/tbe-table-toolbar-actions.tsx create mode 100644 lib/rfqs/tbe-table/tbe-table.tsx create mode 100644 lib/rfqs/validations.ts create mode 100644 lib/rfqs/vendor-table/add-vendor-dialog.tsx create mode 100644 lib/rfqs/vendor-table/comments-sheet.tsx create mode 100644 lib/rfqs/vendor-table/feature-flags-provider.tsx create mode 100644 lib/rfqs/vendor-table/invite-vendors-dialog.tsx create mode 100644 lib/rfqs/vendor-table/vendor-list/vendor-list-table-column.tsx create mode 100644 lib/rfqs/vendor-table/vendor-list/vendor-list-table.tsx create mode 100644 lib/rfqs/vendor-table/vendors-table-columns.tsx create mode 100644 lib/rfqs/vendor-table/vendors-table-floating-bar.tsx create mode 100644 lib/rfqs/vendor-table/vendors-table-toolbar-actions.tsx create mode 100644 lib/rfqs/vendor-table/vendors-table.tsx create mode 100644 lib/roles/repository.ts create mode 100644 lib/roles/services.ts create mode 100644 lib/roles/table/add-role-dialog.tsx create mode 100644 lib/roles/table/assign-roles-sheet.tsx create mode 100644 lib/roles/table/delete-roles-dialog.tsx create mode 100644 lib/roles/table/role-table-toolbar-actions.tsx create mode 100644 lib/roles/table/roles-table-columns.tsx create mode 100644 lib/roles/table/roles-table.tsx create mode 100644 lib/roles/table/update-roles-sheet.tsx create mode 100644 lib/roles/userTable/assginedUsers-table-columns.tsx create mode 100644 lib/roles/userTable/assignedUsers-table.tsx create mode 100644 lib/roles/validations.ts create mode 100644 lib/storage.ts create mode 100644 lib/tag-numbering/repository.ts create mode 100644 lib/tag-numbering/service.ts create mode 100644 lib/tag-numbering/table/feature-flags-provider.tsx create mode 100644 lib/tag-numbering/table/meta-sheet.tsx create mode 100644 lib/tag-numbering/table/tagNumbering-table-columns.tsx create mode 100644 lib/tag-numbering/table/tagNumbering-table-toolbar-actions.tsx create mode 100644 lib/tag-numbering/table/tagNumbering-table.tsx create mode 100644 lib/tag-numbering/validation.ts create mode 100644 lib/tags/form-mapping-service.ts create mode 100644 lib/tags/repository.ts create mode 100644 lib/tags/service.ts create mode 100644 lib/tags/table/add-tag-dialog copy.tsx create mode 100644 lib/tags/table/add-tag-dialog.tsx create mode 100644 lib/tags/table/delete-tags-dialog.tsx create mode 100644 lib/tags/table/feature-flags-provider.tsx create mode 100644 lib/tags/table/tag-table-column.tsx create mode 100644 lib/tags/table/tag-table.tsx create mode 100644 lib/tags/table/tags-export.tsx create mode 100644 lib/tags/table/tags-table-floating-bar.tsx create mode 100644 lib/tags/table/tags-table-toolbar-actions.tsx create mode 100644 lib/tags/table/update-tag-sheet.tsx create mode 100644 lib/tags/validations.ts create mode 100644 lib/tasks/repository.ts create mode 100644 lib/tasks/service.ts create mode 100644 lib/tasks/table/add-tasks-dialog.tsx create mode 100644 lib/tasks/table/delete-tasks-dialog.tsx create mode 100644 lib/tasks/table/feature-flags-provider.tsx create mode 100644 lib/tasks/table/feature-flags.tsx create mode 100644 lib/tasks/table/tasks-table-columns.tsx create mode 100644 lib/tasks/table/tasks-table-floating-bar.tsx create mode 100644 lib/tasks/table/tasks-table-toolbar-actions.tsx create mode 100644 lib/tasks/table/tasks-table.tsx create mode 100644 lib/tasks/table/update-task-sheet.tsx create mode 100644 lib/tasks/utils.ts create mode 100644 lib/tasks/validations.ts create mode 100644 lib/tbe/service.ts create mode 100644 lib/tbe/table/comments-sheet.tsx create mode 100644 lib/tbe/table/feature-flags-provider.tsx create mode 100644 lib/tbe/table/file-dialog.tsx create mode 100644 lib/tbe/table/invite-vendors-dialog.tsx create mode 100644 lib/tbe/table/tbe-table-columns.tsx create mode 100644 lib/tbe/table/tbe-table-toolbar-actions.tsx create mode 100644 lib/tbe/table/tbe-table.tsx create mode 100644 lib/unstable-cache.ts create mode 100644 lib/users/repository.ts create mode 100644 lib/users/send-otp.ts create mode 100644 lib/users/service.ts create mode 100644 lib/users/table/assign-roles-dialog.tsx create mode 100644 lib/users/table/users-table-columns.tsx create mode 100644 lib/users/table/users-table-toolbar-actions.tsx create mode 100644 lib/users/table/users-table.tsx create mode 100644 lib/users/verifyOtp.ts create mode 100644 lib/users/verifyToken.ts create mode 100644 lib/utils.ts create mode 100644 lib/vendor-data/services.ts create mode 100644 lib/vendor-document-list/repository.ts create mode 100644 lib/vendor-document-list/service.ts create mode 100644 lib/vendor-document-list/table/add-doc-dialog.tsx create mode 100644 lib/vendor-document-list/table/delete-docs-dialog.tsx create mode 100644 lib/vendor-document-list/table/doc-table-column.tsx create mode 100644 lib/vendor-document-list/table/doc-table-toolbar-actions.tsx create mode 100644 lib/vendor-document-list/table/doc-table.tsx create mode 100644 "lib/vendor-document-list/table/update-\bdoc-sheet.tsx" create mode 100644 lib/vendor-document-list/validations.ts create mode 100644 lib/vendor-document/repository.ts create mode 100644 lib/vendor-document/service.ts create mode 100644 lib/vendor-document/table/doc-table-column.tsx create mode 100644 lib/vendor-document/table/doc-table-toolbar-actions.tsx create mode 100644 lib/vendor-document/table/doc-table.tsx create mode 100644 lib/vendor-document/validations.ts create mode 100644 lib/vendor-rfq-response/service.ts create mode 100644 lib/vendor-rfq-response/types.ts create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/ItemsDialog.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/attachment-rfq-sheet.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/comments-sheet.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/feature-flags-provider.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-columns.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-toolbar-actions.tsx create mode 100644 lib/vendor-rfq-response/vendor-rfq-table/rfqs-table.tsx create mode 100644 lib/vendor-rfq-response/vendor-tbe-table/comments-sheet.tsx create mode 100644 lib/vendor-rfq-response/vendor-tbe-table/feature-flags-provider.tsx create mode 100644 lib/vendor-rfq-response/vendor-tbe-table/tbe-table-columns.tsx create mode 100644 lib/vendor-rfq-response/vendor-tbe-table/tbe-table.tsx create mode 100644 lib/vendor-rfq-response/vendor-tbe-table/tbeFileHandler.tsx create mode 100644 lib/vendors/contacts-table/add-contact-dialog.tsx create mode 100644 lib/vendors/contacts-table/contact-table-columns.tsx create mode 100644 lib/vendors/contacts-table/contact-table-toolbar-actions.tsx create mode 100644 lib/vendors/contacts-table/contact-table.tsx create mode 100644 lib/vendors/contacts-table/feature-flags-provider.tsx create mode 100644 lib/vendors/items-table/add-item-dialog.tsx create mode 100644 lib/vendors/items-table/feature-flags-provider.tsx create mode 100644 lib/vendors/items-table/item-table-columns.tsx create mode 100644 lib/vendors/items-table/item-table-toolbar-actions.tsx create mode 100644 lib/vendors/items-table/item-table.tsx create mode 100644 lib/vendors/repository.ts create mode 100644 lib/vendors/rfq-history-table/feature-flags-provider.tsx create mode 100644 lib/vendors/rfq-history-table/rfq-history-table-columns.tsx create mode 100644 lib/vendors/rfq-history-table/rfq-history-table-toolbar-actions.tsx create mode 100644 lib/vendors/rfq-history-table/rfq-history-table.tsx create mode 100644 lib/vendors/rfq-history-table/rfq-items-table-dialog.tsx create mode 100644 lib/vendors/service.ts create mode 100644 lib/vendors/table/approve-vendor-dialog.tsx create mode 100644 lib/vendors/table/attachmentButton.tsx create mode 100644 lib/vendors/table/feature-flags-provider.tsx create mode 100644 lib/vendors/table/request-vendor-pg-dialog.tsx create mode 100644 lib/vendors/table/send-vendor-dialog.tsx create mode 100644 lib/vendors/table/update-vendor-sheet.tsx create mode 100644 lib/vendors/table/vendors-table-columns.tsx create mode 100644 lib/vendors/table/vendors-table-floating-bar.tsx create mode 100644 lib/vendors/table/vendors-table-toolbar-actions.tsx create mode 100644 lib/vendors/table/vendors-table.tsx create mode 100644 lib/vendors/validations.ts create mode 100644 middleware.ts create mode 100644 next-i18next.config.js create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pages/api/po/createContractFile.ts create mode 100644 pages/api/po/sendDocuSign.ts create mode 100644 pages/api/po/webhook.ts create mode 100644 postcss.config.mjs create mode 100644 public/file.svg create mode 100644 public/globals.css create mode 100644 public/globe.svg create mode 100644 public/images/02.jpg create mode 100644 public/images/next.svg create mode 100644 public/images/placeholder.svg create mode 100644 public/images/portalBG.png create mode 100644 public/images/vercel.svg create mode 100644 public/locales/en/login.json create mode 100644 public/locales/en/translation.json create mode 100644 public/locales/ko/login.json create mode 100644 public/locales/ko/translation.json create mode 100644 public/next.svg create mode 100644 public/window.svg create mode 100644 statement-breakpoint create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 types/react-file-pond.d.ts create mode 100644 types/table.d.ts create mode 100644 types/user.d.ts create mode 100644 types/vendorData.d.ts diff --git a/.env.development b/.env.development new file mode 100644 index 00000000..68ee314f --- /dev/null +++ b/.env.development @@ -0,0 +1,18 @@ +DATABASE_URL=postgresql://dts:dujinDTS2@localhost:5432/evcp +JWT_SECRET=dujin2tdofj1241.m +NEXTAUTH_SECRET=dujinsejinnejinojin1020202310245jldfmoi2eqjldsajfoadmmdlfjmomc.mv0qp3jem.alsmfmc.jl +Email_Host=email-smtp.ap-northeast-2.amazonaws.com +Email_User_Name=AKIAVLO5MGBR4764VHHW +Email_Password=BGs1NJ784I64GoX/itxaQ60FCUEwf0HZhyaCsEUCwFQ/ +NEXT_PUBLIC_MUI_KEY=da30586e1f20b93856a9783012fc9258Tz04ODI0MyxFPTE3NDQ0NTM2NzgwMDAsUz1wcmVtaXVtLExNPXN1YnNjcmlwdGlvbixLVj0y + + + NEXT_PUBLIC_URL=http://3.36.56.124:3000 + NEXT_PUBLIC_BASE_URL=http://3.36.56.124:3001 + NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd + +# 기간계 시스템 연동 설정 +ERP_API_URL=https://erp.example.com/api/vendors +ERP_API_KEY=your-erp-api-key +ERP_HEALTH_CHECK_URL=https://erp.example.com/api/health + diff --git a/.env.production b/.env.production new file mode 100644 index 00000000..b36728cb --- /dev/null +++ b/.env.production @@ -0,0 +1,18 @@ +DATABASE_URL=postgresql://dts:dujinDTS2@localhost:5432/evcp +JWT_SECRET=dujin2tdofj1241.m +NEXTAUTH_SECRET=dujinsejinnejinojin1020202310245jldfmoi2eqjldsajfoadmmdlfjmomc.mv0qp3jem.alsmfmc.jl +Email_Host=email-smtp.ap-northeast-2.amazonaws.com +Email_User_Name=AKIAVLO5MGBR4764VHHW +Email_Password=BGs1NJ784I64GoX/itxaQ60FCUEwf0HZhyaCsEUCwFQ/ +NEXT_PUBLIC_MUI_KEY=da30586e1f20b93856a9783012fc9258Tz04ODI0MyxFPTE3NDQ0NTM2NzgwMDAsUz1wcmVtaXVtLExNPXN1YnNjcmlwdGlvbixLVj0y + + + NEXT_PUBLIC_URL=https://evcp.dtsolution.io + NEXT_PUBLIC_BASE_URL=https://evcp.dtsolution.io + NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY=demo:1739264618684:616161d7030000000091db1c97c6f386d41d3506ab5b507381ef2ee2bd + +# 기간계 시스템 연동 설정 +ERP_API_URL=https://erp.example.com/api/vendors +ERP_API_KEY=your-erp-api-key +ERP_HEALTH_CHECK_URL=https://erp.example.com/api/health + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4a325b8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +# .env* + +# docusign key +# *.key + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# 형상관리 불필요 파일 +/logs +/uploads + +# 파일시스템에 저장한 첨부파일들 (필요시 추가) +/public/contracts +/public/documents +/public/rfq +/public/uploads +/public/vendors +/public/profiles +/tmp + +# 직접 참조가 불가능해 복사가 필요했던 라이브러리 (pdftrone) +# node_modules/@pdftron/public 경로에서 core 및 ui 경로를 복사해 사용 +/public/pdftronWeb \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..accc4afd --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# SHI eVCP + +## 실행 + +1. 종속성 설치 `npm i` +2. pdfTron 라이브러리 복사 + `@/public/pdftronWeb` 경로에 `core`, `ui` 폴더를 복사 +3. 실행 `npm run dev` + +## 로컬 실행을 위한 DB 준비 + +1. postgres 17 버전을 준비 (환경변수에 따라 계정 및 데이터베이스 생성) +2. drizzle-kit 으로 ORM to Database 처리 +3. 환경변수 선택을 위한 dotenv-cli 설치 `npm i -g dotenv-cli` + +development: + +```bash +npx dotenv -e .env.production -- npx drizzle-kit push +``` + +production: + +```bash +npx dotenv -e .env.development -- npx drizzle-kit push +``` + +3. 필요시 seeding 진행 (`@/db/` 경로 참조, drizzle 문서 확인) + +## 주의사항 + +- 개발한 부분과 관련없는 부분에 대해 포매팅 변경사항을 만들지 마세요. +- 자동 포맷 기능을 종료해두세요 (vscode, prettier, biome 등) + - formatOnSave 옵션을 비활성화하는 설정이 `.vscode/settings.json` 설정에 작성되어 있습니다. + +## 협업전략 + +- main, dev 브랜치에 다이렉트 푸시 X (PR 사용) +- features 브랜치에서 새로운 브랜치 생성하기 + +### github 사용하기 + +1. 조직 내 팀에 개발 인원 추가 +2. 개발 인원은 깃허브 로그인 후 클론 & 작업 내역 PR + +인증 절차는 HTTPS, SSH 등 특정 방식으로 제한하고 있지는 않으며, 적절한 방법으로 진행하시면 됩니다. + +예시 상황: AWS EC2 - CentOS 에서 크레덴셜 셋업하기 + +> ```bash +> # GH 설치하기 +> # 저장소 파일 다운로드 +> sudo curl -L https://cli.github.com/packages/rpm/gh-cli.repo -o /etc/yum.repos.d/gh-cli.repo +> +> # 저장소 활성화 및 설치 +> sudo yum install -y gh +> +> # 인증 처리하기 +> gh auth login # 이후 CLI를 통해 인증처리 진행 +> ``` + +[gh cli에 대해 알아보기](https://github.com/cli/cli) diff --git a/app/[lng]/evcp/bqtbe/page.tsx b/app/[lng]/evcp/bqtbe/page.tsx new file mode 100644 index 00000000..655bd30a --- /dev/null +++ b/app/[lng]/evcp/bqtbe/page.tsx @@ -0,0 +1,72 @@ +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getAllTBE } from "@/lib/rfqs/service" +import { searchParamsTBECache } from "@/lib/rfqs/validations" +import { AllTbeTable } from "@/lib/tbe/table/tbe-table" +import { RfqType } from "@/lib/rfqs/validations" +import * as React from "react" +import { Shell } from "@/components/shell" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + } + searchParams: Promise + rfqType: RfqType +} + +export default async function RfqTBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + + const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정 + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsTBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getAllTBE({ + ...search, + filters: validFilters, + rfqType + } + ) + ]) + + // 4) 렌더링 + return ( + +
+
+
+

+ Technical Bid Evaluation +

+

+ 초대된 벤더에게 TBE를 보낼 수 있습니다.
체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+
+
+ + + } + > + + +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/budgetary/[id]/cbe/page.tsx b/app/[lng]/evcp/budgetary/[id]/cbe/page.tsx new file mode 100644 index 00000000..9a4ae7eb --- /dev/null +++ b/app/[lng]/evcp/budgetary/[id]/cbe/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getCBE, getTBE } from "@/lib/rfqs/service" +import { searchParamsCBECache, } from "@/lib/rfqs/validations" +import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table" +import { CbeTable } from "@/lib/rfqs/cbe-table/cbe-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqTBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsCBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getCBE({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ Commercial Bid Evaluation +

+

+ 초대된 벤더에게 CBE를 보낼 수 있습니다.
체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/budgetary/[id]/layout.tsx b/app/[lng]/evcp/budgetary/[id]/layout.tsx new file mode 100644 index 00000000..39f045e5 --- /dev/null +++ b/app/[lng]/evcp/budgetary/[id]/layout.tsx @@ -0,0 +1,80 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" +import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정 +import { Rfq, RfqWithItems } from "@/db/schema/rfq" +import { findRfqById } from "@/lib/rfqs/service" +import { formatDate } from "@/lib/utils" + +export const metadata: Metadata = { + title: "Vendor Detail", +} + +export default async function RfqLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string, id: string } +}) { + + // 1) URL 파라미터에서 id 추출, Number로 변환 + const resolvedParams = await params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + // 2) DB에서 해당 벤더 정보 조회 + const rfq: RfqWithItems | null = await findRfqById(idAsNumber) + + // 3) 사이드바 메뉴 + const sidebarNavItems = [ + { + title: "Matched Vendors", + href: `/${lng}/evcp/budgetary/${id}`, + }, + { + title: "TBE", + href: `/${lng}/evcp/budgetary/${id}/tbe`, + }, + { + title: "CBE", + href: `/${lng}/evcp/budgetary/${id}/cbe`, + }, + + ] + + return ( + <> +
+
+
+
+ {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */} +

+ {rfq + ? `${rfq.rfqCode ?? ""} 관리` + : "Loading RFQ..."} +

+ +

+ {rfq + ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}` + : ""} +

+

Due Date:{ rfq && {formatDate(rfq?.dueDate)}}

+
+ +
+ +
{children}
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/budgetary/[id]/page.tsx b/app/[lng]/evcp/budgetary/[id]/page.tsx new file mode 100644 index 00000000..f6160574 --- /dev/null +++ b/app/[lng]/evcp/budgetary/[id]/page.tsx @@ -0,0 +1,57 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getMatchedVendors } from "@/lib/rfqs/service" +import { searchParamsMatchedVCache } from "@/lib/rfqs/validations" +import { MatchedVendorsTable } from "@/lib/rfqs/vendor-table/vendors-table" +import { RfqType } from "@/lib/rfqs/validations" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise + rfqType: RfqType +} + +export default async function RfqPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정 + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + const searchParams = await props.searchParams + const search = searchParamsMatchedVCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getMatchedVendors({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ Vendors +

+

+ 등록된 벤더 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다.
"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/budgetary/[id]/tbe/page.tsx b/app/[lng]/evcp/budgetary/[id]/tbe/page.tsx new file mode 100644 index 00000000..a6259696 --- /dev/null +++ b/app/[lng]/evcp/budgetary/[id]/tbe/page.tsx @@ -0,0 +1,55 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getTBE } from "@/lib/rfqs/service" +import { searchParamsTBECache } from "@/lib/rfqs/validations" +import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqTBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsTBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTBE({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ Technical Bid Evaluation +

+

+ 초대된 벤더에게 TBE를 보낼 수 있습니다.
체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/budgetary/page.tsx b/app/[lng]/evcp/budgetary/page.tsx new file mode 100644 index 00000000..04550353 --- /dev/null +++ b/app/[lng]/evcp/budgetary/page.tsx @@ -0,0 +1,86 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { searchParamsCache } from "@/lib/rfqs/validations" +import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs/service" +import { RfqsTable } from "@/lib/rfqs/table/rfqs-table" +import { getAllItems } from "@/lib/items/service" +import { RfqType } from "@/lib/rfqs/validations" +import { Ellipsis } from "lucide-react" + +interface RfqPageProps { + searchParams: Promise; + rfqType: RfqType; + title: string; + description: string; +} + +export default async function RfqPage({ + searchParams, + rfqType = RfqType.BUDGETARY, + title = "Budgetary Quote", + description = "Budgetary Quote를 등록하여 요청 및 응답을 관리할 수 있습니다." +}: RfqPageProps) { + const search = searchParamsCache.parse(await searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getRfqs({ + ...search, + filters: validFilters, + rfqType // 전달받은 rfqType 사용 + }), + getRfqStatusCounts(rfqType), // rfqType 전달 + getAllItems() + ]) + + return ( + +
+
+
+

+ {title} +

+

+ {description} + 기본적인 정보와 RFQ를 위한 아이템 등록 및 첨부를 한 후, + + + 버튼 + 을 클릭하면 "Proceed"를 통해 상세화면으로 이동하여 진행할 수 있습니다. +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/equip-class/page.tsx b/app/[lng]/evcp/equip-class/page.tsx new file mode 100644 index 00000000..fcda1c1d --- /dev/null +++ b/app/[lng]/evcp/equip-class/page.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsCache } from "@/lib/equip-class/validation" +import { FormListsTable } from "@/lib/form-list/table/formLists-table" +import { getTagClassists } from "@/lib/equip-class/service" +import { EquipClassTable } from "@/lib/equip-class/table/equipClass-table" + + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTagClassists({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + +
+
+
+

+ Object Class List from S-EDP +

+

+ 벤더 데이터 입력을 위한 Form 리스트입니다.{" "} + {/* + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/faq/manage/actions.ts b/app/[lng]/evcp/faq/manage/actions.ts new file mode 100644 index 00000000..bc443a8a --- /dev/null +++ b/app/[lng]/evcp/faq/manage/actions.ts @@ -0,0 +1,48 @@ +'use server'; + +import { promises as fs } from 'fs'; +import path from 'path'; +import { FaqCategory } from '@/components/faq/FaqCard'; +import { fallbackLng } from '@/i18n/settings'; + +const FAQ_CONFIG_PATH = path.join(process.cwd(), 'config', 'faqDataConfig.ts'); + +export async function updateFaqData(lng: string, newData: FaqCategory[]) { + try { + const fileContent = await fs.readFile(FAQ_CONFIG_PATH, 'utf-8'); + const dataMatch = fileContent.match(/export const faqCategories[^=]*=\s*(\{[\s\S]*\});/); + if (!dataMatch) { + throw new Error('FAQ 데이터 형식이 올바르지 않습니다.'); + } + + const allData = eval(`(${dataMatch[1]})`); + const updatedData = { + ...allData, + [lng]: newData + }; + + const newFileContent = `import { FaqCategory } from "@/components/faq/FaqCard";\n\ninterface LocalizedFaqCategories {\n [lng: string]: FaqCategory[];\n}\n\nexport const faqCategories: LocalizedFaqCategories = ${JSON.stringify(updatedData, null, 4)};`; + await fs.writeFile(FAQ_CONFIG_PATH, newFileContent, 'utf-8'); + + return { success: true }; + } catch (error) { + console.error('FAQ 데이터 업데이트 중 오류 발생:', error); + return { success: false, error: '데이터 업데이트 중 오류가 발생했습니다.' }; + } +} + +export async function getFaqData(lng: string): Promise<{ data: FaqCategory[] }> { + try { + const fileContent = await fs.readFile(FAQ_CONFIG_PATH, 'utf-8'); + const dataMatch = fileContent.match(/export const faqCategories[^=]*=\s*(\{[\s\S]*\});/); + if (!dataMatch) { + throw new Error('FAQ 데이터 형식이 올바르지 않습니다.'); + } + + const allData = eval(`(${dataMatch[1]})`); + return { data: allData[lng] || allData[fallbackLng] || [] }; + } catch (error) { + console.error('FAQ 데이터 읽기 중 오류 발생:', error); + return { data: [] }; + } +} \ No newline at end of file diff --git a/app/[lng]/evcp/faq/manage/page.tsx b/app/[lng]/evcp/faq/manage/page.tsx new file mode 100644 index 00000000..011bbfa4 --- /dev/null +++ b/app/[lng]/evcp/faq/manage/page.tsx @@ -0,0 +1,38 @@ +import { FaqManager } from '@/components/faq/FaqManager'; +import { getFaqData, updateFaqData } from './actions'; +import { revalidatePath } from 'next/cache'; +import { FaqCategory } from '@/components/faq/FaqCard'; + +interface Props { + params: { + lng: string; + } +} + +export default async function FaqManagePage(props: Props) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const { data } = await getFaqData(lng); + + async function handleSave(newData: FaqCategory[]) { + 'use server'; + await updateFaqData(lng, newData); + revalidatePath(`/${lng}/evcp/faq`); + } + + return ( +
+
+
+
+

FAQ Management

+

+ Manage FAQ categories and items for {lng.toUpperCase()} language. +

+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/app/[lng]/evcp/faq/page.tsx b/app/[lng]/evcp/faq/page.tsx new file mode 100644 index 00000000..9b62b7e4 --- /dev/null +++ b/app/[lng]/evcp/faq/page.tsx @@ -0,0 +1,62 @@ +import { Separator } from "@/components/ui/separator" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { faqCategories } from "@/config/faqDataConfig" +import { FaqCard } from "@/components/faq/FaqCard" +import { Button } from "@/components/ui/button" +import { Settings } from "lucide-react" +import Link from "next/link" +import { fallbackLng } from "@/i18n/settings" + +interface Props { + params: { + lng: string; + } +} + +export default async function FaqPage(props: Props) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const localizedFaqCategories = faqCategories[lng] || faqCategories[fallbackLng]; + + return ( +
+
+
+
+
+

Frequently Asked Questions

+

+ Find answers to common questions about using the EVCP system. +

+
+ + + +
+ + + + + {localizedFaqCategories.map((category) => ( + + {category.label} + + ))} + + + {localizedFaqCategories.map((category) => ( + + {category.items.map((item, index) => ( + + ))} + + ))} + +
+
+
+ ) +} diff --git a/app/[lng]/evcp/form-list/page.tsx b/app/[lng]/evcp/form-list/page.tsx new file mode 100644 index 00000000..f96917d6 --- /dev/null +++ b/app/[lng]/evcp/form-list/page.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsCache } from "@/lib/form-list/validation" +import { ItemsTable } from "@/lib/items/table/items-table" +import { getFormLists } from "@/lib/form-list/service" +import { FormListsTable } from "@/lib/form-list/table/formLists-table" + + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getFormLists({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + +
+
+
+

+ Form List from S-EDP +

+

+ 벤더 데이터 입력을 위한 Form 리스트입니다.{" "} + {/* + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/items/page.tsx b/app/[lng]/evcp/items/page.tsx new file mode 100644 index 00000000..144689ff --- /dev/null +++ b/app/[lng]/evcp/items/page.tsx @@ -0,0 +1,74 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsCache } from "@/lib/items/validations" +import { getItems } from "@/lib/items/service" +import { ItemsTable } from "@/lib/items/table/items-table" + + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getItems({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + +
+
+
+

+ Package Items +

+

+ Item을 등록하고 관리할 수 있습니다.{" "} + {/* + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/layout.tsx b/app/[lng]/evcp/layout.tsx new file mode 100644 index 00000000..9dc39f7b --- /dev/null +++ b/app/[lng]/evcp/layout.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from 'react'; +import { Header } from '@/components/layout/Header'; +import { SiteFooter } from '@/components/layout/Footer'; + +export default function EvcpLayout({ children }: { children: ReactNode }) { + return ( +
+
+
+
+ {children} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/app/[lng]/evcp/page.tsx b/app/[lng]/evcp/page.tsx new file mode 100644 index 00000000..a1e9f8be --- /dev/null +++ b/app/[lng]/evcp/page.tsx @@ -0,0 +1,8 @@ + +export default function Pages() { + return ( + <> + test + + ) + } \ No newline at end of file diff --git a/app/[lng]/evcp/po/page.tsx b/app/[lng]/evcp/po/page.tsx new file mode 100644 index 00000000..fa528df0 --- /dev/null +++ b/app/[lng]/evcp/po/page.tsx @@ -0,0 +1,65 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getPOs } from "@/lib/po/service" +import { searchParamsCache } from "@/lib/po/validations" +import { PoListsTable } from "@/lib/po/table/po-table" + + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getPOs({ + ...search, + filters: validFilters, + }), + ]) + + return ( + + +
+
+
+

+ PO 확인 및 전자서명 +

+

+ 기간계 시스템으로부터 PO를 확인하고 벤더에게 전자서명을 요청할 수 있습니다. 요쳥된 전자서명의 이력 또한 확인할 수 있습니다. + +

+
+
+
+ + + }> + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/pq-criteria/page.tsx b/app/[lng]/evcp/pq-criteria/page.tsx new file mode 100644 index 00000000..d924890d --- /dev/null +++ b/app/[lng]/evcp/pq-criteria/page.tsx @@ -0,0 +1,71 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { searchParamsCache } from "@/lib/pq/validations" +import { getPQs } from "@/lib/pq/service" +import { PqsTable } from "@/lib/pq/table/pq-table" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getPQs({ + ...search, + filters: validFilters, + }), + ]) + + return ( + + +
+
+
+

+ Pre-Qualification Check Sheet +

+

+ 벤더 등록을 위한, 벤더가 제출할 PQ 항목을 관리할 수 있습니다. + +

+
+
+
+ + + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/pq/[vendorId]/page.tsx b/app/[lng]/evcp/pq/[vendorId]/page.tsx new file mode 100644 index 00000000..cb4277f1 --- /dev/null +++ b/app/[lng]/evcp/pq/[vendorId]/page.tsx @@ -0,0 +1,38 @@ +import * as React from "react" +import { Shell } from "@/components/shell" +import { Skeleton } from "@/components/ui/skeleton" + +import { type SearchParams } from "@/types/table" +import { getPQDataByVendorId } from "@/lib/pq/service" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Vendor } from "@/db/schema/vendors" +import { findVendorById } from "@/lib/vendors/service" +import VendorPQReviewPage from "@/components/pq/pq-review-detail" +import VendorPQAdminReview from "@/components/pq/pq-review-detail" + +interface IndexPageProps { + params: { + vendorId: string // Updated from 'id' to 'contractId' to match route parameter + } + searchParams: Promise +} + +export default async function DocumentListPage(props: IndexPageProps) { + const resolvedParams = await props.params + const vendorId = resolvedParams.vendorId // Updated from 'id' to 'contractId' + + const idAsNumber = Number(vendorId) + + const data = await getPQDataByVendorId(idAsNumber) + + const vendor: Vendor | null = await findVendorById(idAsNumber) + + // 4) 렌더링 + return ( + + {vendor && + + } + + ) +} diff --git a/app/[lng]/evcp/pq/page.tsx b/app/[lng]/evcp/pq/page.tsx new file mode 100644 index 00000000..46b22b12 --- /dev/null +++ b/app/[lng]/evcp/pq/page.tsx @@ -0,0 +1,71 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { getVendorsInPQ } from "@/lib/pq/service" +import { searchParamsCache } from "@/lib/vendors/validations" +import { VendorsPQReviewTable } from "@/lib/pq/pq-review-table/vendors-table" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getVendorsInPQ({ + ...search, + filters: validFilters, + }), + ]) + + return ( + + +
+
+
+

+ Pre-Qualification Review +

+

+ 벤더가 제출한 PQ를 확인하고 수정 요청 등을 할 수 있으며 PQ 종료 후에는 통과 여부를 결정할 수 있습니다. + +

+
+
+
+ + + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/report/page.tsx b/app/[lng]/evcp/report/page.tsx new file mode 100644 index 00000000..a1e9f8be --- /dev/null +++ b/app/[lng]/evcp/report/page.tsx @@ -0,0 +1,8 @@ + +export default function Pages() { + return ( + <> + test + + ) + } \ No newline at end of file diff --git a/app/[lng]/evcp/rfq/[id]/cbe/page.tsx b/app/[lng]/evcp/rfq/[id]/cbe/page.tsx new file mode 100644 index 00000000..bc32641f --- /dev/null +++ b/app/[lng]/evcp/rfq/[id]/cbe/page.tsx @@ -0,0 +1,53 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsTBECache } from "@/lib/rfqs/validations" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqCBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsTBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // const promises = Promise.all([ + // getCBE({ + // ...search, + // filters: validFilters, + // }, + // idAsNumber) + // ]) + + // 4) 렌더링 + return ( +
+
+

+ Technical Bid Evaluation +

+

+ 초대된 벤더에게 CBE를 보낼 수 있습니다.
"발행하기" 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/rfq/[id]/layout.tsx b/app/[lng]/evcp/rfq/[id]/layout.tsx new file mode 100644 index 00000000..2aac90eb --- /dev/null +++ b/app/[lng]/evcp/rfq/[id]/layout.tsx @@ -0,0 +1,80 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" +import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정 +import { Rfq, RfqWithItems } from "@/db/schema/rfq" +import { findRfqById } from "@/lib/rfqs/service" +import { formatDate } from "@/lib/utils" + +export const metadata: Metadata = { + title: "Vendor Detail", +} + +export default async function RfqLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string, id: string } +}) { + + // 1) URL 파라미터에서 id 추출, Number로 변환 + const resolvedParams = await params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + // 2) DB에서 해당 벤더 정보 조회 + const rfq: RfqWithItems | null = await findRfqById(idAsNumber) + + // 3) 사이드바 메뉴 + const sidebarNavItems = [ + { + title: "Matched Vendors", + href: `/${lng}/evcp/rfq/${id}`, + }, + { + title: "TBE", + href: `/${lng}/evcp/rfq/${id}/tbe`, + }, + { + title: "CBE", + href: `/${lng}/evcp/rfq/${id}/cbe`, + }, + + ] + + return ( + <> +
+
+
+
+ {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */} +

+ {rfq + ? `${rfq.rfqCode ?? ""} 관리` + : "Loading RFQ..."} +

+ +

+ {rfq + ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}` + : ""} +

+

Due Date:{ rfq && {formatDate(rfq?.dueDate)}}

+
+ +
+ +
{children}
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/rfq/[id]/page.tsx b/app/[lng]/evcp/rfq/[id]/page.tsx new file mode 100644 index 00000000..026ca5ac --- /dev/null +++ b/app/[lng]/evcp/rfq/[id]/page.tsx @@ -0,0 +1,55 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getMatchedVendors } from "@/lib/rfqs/service" +import { searchParamsMatchedVCache } from "@/lib/rfqs/validations" +import { MatchedVendorsTable } from "@/lib/rfqs/vendor-table/vendors-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsMatchedVCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getMatchedVendors({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ Vendors +

+

+ 등록된 벤더 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다.
"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/rfq/[id]/tbe/page.tsx b/app/[lng]/evcp/rfq/[id]/tbe/page.tsx new file mode 100644 index 00000000..15c5d93c --- /dev/null +++ b/app/[lng]/evcp/rfq/[id]/tbe/page.tsx @@ -0,0 +1,55 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getTBE } from "@/lib/rfqs/service" +import { searchParamsTBECache } from "@/lib/rfqs/validations" +import { TbeTable } from "@/lib/rfqs/tbe-table/tbe-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqTBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsTBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTBE({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ Technical Bid Evaluation +

+

+ 초대된 벤더에게 TBE를 보낼 수 있습니다.
"발행하기" 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/rfq/page.tsx b/app/[lng]/evcp/rfq/page.tsx new file mode 100644 index 00000000..3417b0bf --- /dev/null +++ b/app/[lng]/evcp/rfq/page.tsx @@ -0,0 +1,80 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +import { searchParamsCache } from "@/lib/rfqs/validations" +import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs/service" +import { RfqsTable } from "@/lib/rfqs/table/rfqs-table" +import { getAllItems } from "@/lib/items/service" +import { RfqType } from "@/lib/rfqs/validations" + +interface RfqPageProps { + searchParams: Promise; + rfqType: RfqType; + title: string; + description: string; +} + +export default async function RfqPage({ + searchParams, + rfqType = RfqType.PURCHASE, + title = "RFQ", + description = "RFQ를 등록하고 관리할 수 있습니다." +}: RfqPageProps) { + const search = searchParamsCache.parse(await searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getRfqs({ + ...search, + filters: validFilters, + rfqType // 전달받은 rfqType 사용 + }), + getRfqStatusCounts(rfqType), // rfqType 전달 + getAllItems() + ]) + + return ( + +
+
+
+

+ {title} +

+

+ {description} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/settings/layout.tsx b/app/[lng]/evcp/settings/layout.tsx new file mode 100644 index 00000000..6f373567 --- /dev/null +++ b/app/[lng]/evcp/settings/layout.tsx @@ -0,0 +1,68 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" + +export const metadata: Metadata = { + title: "Settings", + // description: "Advanced form example using react-hook-form and Zod.", +} + + +interface SettingsLayoutProps { + children: React.ReactNode + params: { lng: string } +} + +export default async function SettingsLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string } +}) { + const resolvedParams = await params + const lng = resolvedParams.lng + + + const sidebarNavItems = [ + + { + title: "Account", + href: `/${lng}/evcp/settings`, + }, + { + title: "Preferences", + href: `/${lng}/evcp/settings/preferences`, + } + + + ] + + + return ( + <> +
+
+
+
+

Settings

+

+ Manage your account settings and preferences. +

+
+ +
+ +
{children}
+
+
+
+
+ + + + ) +} diff --git a/app/[lng]/evcp/settings/page.tsx b/app/[lng]/evcp/settings/page.tsx new file mode 100644 index 00000000..a6eaac90 --- /dev/null +++ b/app/[lng]/evcp/settings/page.tsx @@ -0,0 +1,18 @@ +import { Separator } from "@/components/ui/separator" +import { AccountForm } from "@/components/settings/account-form" + +export default function SettingsAccountPage() { + return ( +
+
+

Account

+

+ Update your account settings. Set your preferred language and + timezone. +

+
+ + +
+ ) +} diff --git a/app/[lng]/evcp/settings/preferences/page.tsx b/app/[lng]/evcp/settings/preferences/page.tsx new file mode 100644 index 00000000..e2a88021 --- /dev/null +++ b/app/[lng]/evcp/settings/preferences/page.tsx @@ -0,0 +1,17 @@ +import { Separator } from "@/components/ui/separator" +import { AppearanceForm } from "@/components/settings/appearance-form" + +export default function SettingsAppearancePage() { + return ( +
+
+

Preference

+

+ Customize the preference of the app. +

+
+ + +
+ ) +} diff --git a/app/[lng]/evcp/system/admin-users/page.tsx b/app/[lng]/evcp/system/admin-users/page.tsx new file mode 100644 index 00000000..11a9e9fb --- /dev/null +++ b/app/[lng]/evcp/system/admin-users/page.tsx @@ -0,0 +1,60 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { DateRangePicker } from "@/components/date-range-picker" +import { Separator } from "@/components/ui/separator" + +import { searchParamsCache } from "@/lib/admin-users/validations" +import { getAllCompanies, getAllRoles, getUserCountGroupByCompany, getUserCountGroupByRole, getUsers } from "@/lib/admin-users/service" +import { AdmUserTable } from "@/lib/admin-users/table/ausers-table" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function UserTable(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getUsers({ + ...search, + filters: validFilters, + }), + getUserCountGroupByCompany(), + getUserCountGroupByRole(), + getAllCompanies(), + getAllRoles() + ]) + + return ( + + } + > +
+
+

Vendor Admin User Management

+

+ 협력업체의 유저 전체를 조회하고 어드민 유저를 생성할 수 있는 페이지입니다. 이곳에서 초기 유저를 생성시킬 수 있습니다.
생성 후에는 생성된 사용자의 이메일로 생성 통보 이메일이 발송되며 사용자는 이메일과 OTP로 로그인이 가능합니다. +

+
+ + +
+
+ + ) +} diff --git a/app/[lng]/evcp/system/layout.tsx b/app/[lng]/evcp/system/layout.tsx new file mode 100644 index 00000000..4885a028 --- /dev/null +++ b/app/[lng]/evcp/system/layout.tsx @@ -0,0 +1,75 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" + +export const metadata: Metadata = { + title: "System Setting", + // description: "Advanced form example using react-hook-form and Zod.", +} + + +interface SettingsLayoutProps { + children: React.ReactNode + params: { lng: string } +} + +export default async function SettingsLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string } +}) { + const resolvedParams = await params + const lng = resolvedParams.lng + + + const sidebarNavItems = [ + + { + title: "Users", + href: `/${lng}/evcp/system`, + }, + { + title: "Roles", + href: `/${lng}/evcp/system/roles`, + }, + { + title: "Permissions", + href: `/${lng}/evcp/system/permissions`, + }, + { + title: "Vendor Users", + href: `/${lng}/evcp/system/admin-users`, + }, + + ] + + + return ( + <> +
+
+
+
+

시스템 설정

+

+ 사용자, 롤, 접근 권한을 관리하세요. +

+
+ +
+ +
{children}
+
+
+
+
+ + + + ) +} diff --git a/app/[lng]/evcp/system/page.tsx b/app/[lng]/evcp/system/page.tsx new file mode 100644 index 00000000..2d180028 --- /dev/null +++ b/app/[lng]/evcp/system/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import * as React from "react" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsCache } from "@/lib/admin-users/validations" +import { getAllRoles, getUsersEVCP } from "@/lib/users/service" +import { getUserCountGroupByRole } from "@/lib/admin-users/service" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { UserTable } from "@/lib/users/table/users-table" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function SystemUserPage(props: IndexPageProps) { + + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getUsersEVCP({ + ...search, + filters: validFilters, + }), + getUserCountGroupByRole(), + getAllRoles() + ]) + + return ( + + } + > +
+
+

Users

+

+ 시스템 전체 사용자들을 조회하고 관리할 수 있는 페이지입니다. 사용자에게 롤을 할당하는 것으로 메뉴별 권한을 관리할 수 있습니다. +

+
+ + +
+
+ + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/system/permissions/page.tsx b/app/[lng]/evcp/system/permissions/page.tsx new file mode 100644 index 00000000..6aa2b693 --- /dev/null +++ b/app/[lng]/evcp/system/permissions/page.tsx @@ -0,0 +1,17 @@ +import PermissionsTree from "@/components/system/permissionsTree" +import { Separator } from "@/components/ui/separator" + +export default function PermissionsPage() { + return ( +
+
+

Permissions

+

+ Set permissions to the menu by Role +

+
+ + +
+ ) +} diff --git a/app/[lng]/evcp/system/roles/page.tsx b/app/[lng]/evcp/system/roles/page.tsx new file mode 100644 index 00000000..fe074600 --- /dev/null +++ b/app/[lng]/evcp/system/roles/page.tsx @@ -0,0 +1,68 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Separator } from "@/components/ui/separator" + +import { searchParamsCache } from "@/lib/roles/validations" +import { searchParamsCache as searchParamsCache2 } from "@/lib/admin-users/validations" +import { RolesTable } from "@/lib/roles/table/roles-table" +import { getRolesWithCount } from "@/lib/roles/services" +import { getUsersAll } from "@/lib/users/service" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function UserTable(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + const search2 = searchParamsCache2.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getRolesWithCount({ + ...search, + filters: validFilters, + }), + + + ]) + + + const promises2 = Promise.all([ + getUsersAll({ + ...search2, + filters: validFilters, + }, "evcp"), + ]) + + + return ( + + } + > +
+
+

Role Management

+

+ 역할을 생성하고 역할에 유저를 할당할 수 있는 페이지입니다. 역할에 메뉴의 접근 권한 역시 할당할 수 있습니다. +

+
+ + +
+
+ + ) +} diff --git a/app/[lng]/evcp/tag-numbering/page.tsx b/app/[lng]/evcp/tag-numbering/page.tsx new file mode 100644 index 00000000..9d5b903a --- /dev/null +++ b/app/[lng]/evcp/tag-numbering/page.tsx @@ -0,0 +1,74 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsCache } from "@/lib/tag-numbering/validation" +import { getTagNumbering } from "@/lib/tag-numbering/service" +import { TagNumberingTable } from "@/lib/tag-numbering/table/tagNumbering-table" + + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTagNumbering({ + ...search, + filters: validFilters, + }), + + ]) + + return ( + +
+
+
+

+ Tag Numbering from S-EDP +

+

+ 태그 넘버링을 위한 룰셋을 S-EDP로부터 가져오고 확인할 수 있습니다{" "} + {/* + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/evcp/tasks/page.tsx b/app/[lng]/evcp/tasks/page.tsx new file mode 100644 index 00000000..f14cc757 --- /dev/null +++ b/app/[lng]/evcp/tasks/page.tsx @@ -0,0 +1,63 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { DateRangePicker } from "@/components/date-range-picker" +import { Shell } from "@/components/shell" + +import { FeatureFlagsProvider } from "@/lib/tasks/table/feature-flags-provider" +import { TasksTable } from "@/lib/tasks/table/tasks-table" +import { + getTaskPriorityCounts, + getTasks, + getTaskStatusCounts, +} from "@/lib/tasks/service" +import { searchParamsCache } from "@/lib/tasks/validations" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTasks({ + ...search, + filters: validFilters, + }), + getTaskStatusCounts(), + getTaskPriorityCounts(), + ]) + + return ( + + }> + {/* */} + + + } + > + + + + ) +} diff --git a/app/[lng]/evcp/vendors/[id]/info/items/page.tsx b/app/[lng]/evcp/vendors/[id]/info/items/page.tsx new file mode 100644 index 00000000..e9ff17b4 --- /dev/null +++ b/app/[lng]/evcp/vendors/[id]/info/items/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { getVendorItems } from "@/lib/vendors/service" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsItemCache } from "@/lib/vendors/validations" +import { VendorItemsTable } from "@/lib/vendors/items-table/item-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function SettingsAccountPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsItemCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + + + const promises = Promise.all([ + getVendorItems({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + // 4) 렌더링 + return ( +
+
+

+ Possible Items +

+

+ 딜리버리가 가능한 아이템 리스트를 확인할 수 있습니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/vendors/[id]/info/layout.tsx b/app/[lng]/evcp/vendors/[id]/info/layout.tsx new file mode 100644 index 00000000..39e0bac0 --- /dev/null +++ b/app/[lng]/evcp/vendors/[id]/info/layout.tsx @@ -0,0 +1,79 @@ +import { Metadata } from "next" + +import { Separator } from "@/components/ui/separator" +import { SidebarNav } from "@/components/layout/sidebar-nav" +import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정 +import { Vendor } from "@/db/schema/vendors" + +export const metadata: Metadata = { + title: "Vendor Detail", +} + +export default async function SettingsLayout({ + children, + params, +}: { + children: React.ReactNode + params: { lng: string , id: string} +}) { + + // 1) URL 파라미터에서 id 추출, Number로 변환 + const resolvedParams = await params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + // 2) DB에서 해당 벤더 정보 조회 + const vendor: Vendor | null = await findVendorById(idAsNumber) + + // 3) 사이드바 메뉴 + const sidebarNavItems = [ + { + title: "Contacts", + href: `/${lng}/evcp/vendors/${id}/info`, + }, + { + title: "Items", + href: `/${lng}/evcp/vendors/${id}/info/items`, + }, + { + title: "RFQ History", + href: `/${lng}/evcp/vendors/${id}/info/rfq-history`, + }, + { + title: "Bidding History", + href: `/${lng}/evcp/vendors/${id}/info/bid-history`, + }, + { + title: "Contract History", + href: `/${lng}/evcp/vendors/${id}/info/contract-history`, + }, + ] + + return ( + <> +
+
+
+
+ {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */} +

+ {vendor + ? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보` + : "Loading Vendor..."} +

+

벤더 관련 상세사항을 확인하세요.

+
+ +
+ +
{children}
+
+
+
+
+ + ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/vendors/[id]/info/page.tsx b/app/[lng]/evcp/vendors/[id]/info/page.tsx new file mode 100644 index 00000000..6279e924 --- /dev/null +++ b/app/[lng]/evcp/vendors/[id]/info/page.tsx @@ -0,0 +1,56 @@ +import { Separator } from "@/components/ui/separator" +import { getVendorContacts } from "@/lib/vendors/service" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsContactCache } from "@/lib/vendors/validations" +import { VendorContactsTable } from "@/lib/vendors/contacts-table/contact-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function SettingsAccountPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsContactCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + + + const promises = Promise.all([ + getVendorContacts({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + // 4) 렌더링 + return ( +
+
+

+ Contacts +

+

+ 업무별 담당자 정보를 확인하세요. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/vendors/[id]/info/rfq-history/page.tsx b/app/[lng]/evcp/vendors/[id]/info/rfq-history/page.tsx new file mode 100644 index 00000000..1d2f618c --- /dev/null +++ b/app/[lng]/evcp/vendors/[id]/info/rfq-history/page.tsx @@ -0,0 +1,55 @@ +import { Separator } from "@/components/ui/separator" +import { getRfqHistory } from "@/lib/vendors/service" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsRfqHistoryCache } from "@/lib/vendors/validations" +import { VendorRfqHistoryTable } from "@/lib/vendors/rfq-history-table/rfq-history-table" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqHistoryPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsRfqHistoryCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getRfqHistory({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+

+ RFQ History +

+

+ 벤더의 RFQ 참여 이력을 확인할 수 있습니다. +

+
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/evcp/vendors/page.tsx b/app/[lng]/evcp/vendors/page.tsx new file mode 100644 index 00000000..e3cc7fdc --- /dev/null +++ b/app/[lng]/evcp/vendors/page.tsx @@ -0,0 +1,78 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" + +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + + +import { searchParamsCache } from "@/lib/vendors/validations" +import { getVendors, getVendorStatusCounts } from "@/lib/vendors/service" +import { VendorsTable } from "@/lib/vendors/table/vendors-table" +import { Ellipsis } from "lucide-react" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getVendors({ + ...search, + filters: validFilters, + }), + getVendorStatusCounts(), + ]) + + return ( + + +
+
+
+

+ Vendor Information +

+

+ 벤더에 대한 요약 정보를 확인하고{" "} + + + 버튼 + + 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다.
벤더의 상태에 따라 가입을 승인해주거나 PQ 요청을 할 수 있고 검토가 완료된 벤더를 기간계 시스템에 전송하여 벤더 코드를 따올 수 있습니다. +

+
+
+
+ + + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/login/page.tsx b/app/[lng]/login/page.tsx new file mode 100644 index 00000000..0e3a498c --- /dev/null +++ b/app/[lng]/login/page.tsx @@ -0,0 +1,15 @@ +import { LoginForm } from "@/components/login/login-form" +import { Suspense } from "react" + +export default function LoginPage() { + return ( + Loading login form...}> + + + //
+ //
+ + //
+ //
+ ) +} diff --git a/app/[lng]/partners/(partners)/dashboard/page.tsx b/app/[lng]/partners/(partners)/dashboard/page.tsx new file mode 100644 index 00000000..a1e9f8be --- /dev/null +++ b/app/[lng]/partners/(partners)/dashboard/page.tsx @@ -0,0 +1,8 @@ + +export default function Pages() { + return ( + <> + test + + ) + } \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/document-list/[contractId]/page.tsx b/app/[lng]/partners/(partners)/document-list/[contractId]/page.tsx new file mode 100644 index 00000000..65df0b1f --- /dev/null +++ b/app/[lng]/partners/(partners)/document-list/[contractId]/page.tsx @@ -0,0 +1,44 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { searchParamsCache } from "@/lib/vendor-document-list/validations" +import { getVendorDocuments } from "@/lib/vendor-document-list/service" +import { DocumentsTable } from "@/lib/vendor-document-list/table/doc-table" + +interface IndexPageProps { + params: { + contractId: string // Updated from 'id' to 'contractId' to match route parameter + } + searchParams: Promise +} + +export default async function DocumentListPage(props: IndexPageProps) { + const resolvedParams = await props.params + const contractId = resolvedParams.contractId // Updated from 'id' to 'contractId' + + const idAsNumber = Number(contractId) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const projectType = searchParams.projectType === "plant" ? "plant" : "ship" + + const promises = Promise.all([ + getVendorDocuments({ + ...search, + filters: validFilters, + }, idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/document-list/layout.tsx b/app/[lng]/partners/(partners)/document-list/layout.tsx new file mode 100644 index 00000000..a75cdf7d --- /dev/null +++ b/app/[lng]/partners/(partners)/document-list/layout.tsx @@ -0,0 +1,45 @@ + +import { cookies } from "next/headers" +import { Shell } from "@/components/shell" +import DocumentContainer from "@/components/documents/document-container" +import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services" +import { getVendorDocumentLists } from "@/lib/vendor-document/service" +import VendorDocumentsClient from "@/components/documents/vendor-docs.client" +import VendorDocumentListClient from "@/components/document-lists/vendor-doc-list-client" + + + +// Layout 컴포넌트는 서버 컴포넌트입니다 +export default async function VendorDocuments({ + children, +}: { + children: React.ReactNode +}) { + // const session = await getServerSession(authOptions) + // const vendorId = session?.user.companyId + const vendorId = "17" + const idAsNumber = Number(vendorId) + + const projects = await getVendorProjectsAndContracts(idAsNumber) + + + // 레이아웃 설정 쿠키 가져오기 + // Next.js 15에서는 cookies()가 Promise를 반환하므로 await 사용 + const cookieStore = await cookies() + + // 이제 cookieStore.get() 메서드 사용 가능 + const layout = cookieStore.get("react-resizable-panels:layout:mail") + const collapsed = cookieStore.get("react-resizable-panels:collapsed") + + const defaultLayout = layout ? JSON.parse(layout.value) : undefined + const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined + + + return ( + + + {children} + + + ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/document-list/page.tsx b/app/[lng]/partners/(partners)/document-list/page.tsx new file mode 100644 index 00000000..721eb408 --- /dev/null +++ b/app/[lng]/partners/(partners)/document-list/page.tsx @@ -0,0 +1,21 @@ +// app/vendor-data/page.tsx +import * as React from "react" +import { Separator } from "@/components/ui/separator" + +export default async function IndexPage() { + return ( +
+ +
+
+

시작하는 방법

+

+ 오른쪽 상단에서 프로젝트/계약을 선택하세요.
+ + +

+
+
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/documents/[contractId]/page.tsx b/app/[lng]/partners/(partners)/documents/[contractId]/page.tsx new file mode 100644 index 00000000..7bf50c15 --- /dev/null +++ b/app/[lng]/partners/(partners)/documents/[contractId]/page.tsx @@ -0,0 +1,47 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { TagsTable } from "@/lib/tags/table/tag-table" +import { searchParamsCache } from "@/lib/vendor-document/validations" +import { getTags } from "@/lib/tags/service" +import { getVendorDocumentLists } from "@/lib/vendor-document/service" +import { DocumentListTable } from "@/lib/vendor-document/table/doc-table" +import DocumentContainer from "@/components/documents/document-container" + +interface IndexPageProps { + params: { + contractId: string // Updated from 'id' to 'contractId' to match route parameter + } + searchParams: Promise +} + +export default async function DocumentListPage(props: IndexPageProps) { + const resolvedParams = await props.params + const contractId = resolvedParams.contractId // Updated from 'id' to 'contractId' + + const idAsNumber = Number(contractId) + + console.log(idAsNumber) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getVendorDocumentLists({ + ...search, + filters: validFilters, + }, idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/documents/layout.tsx b/app/[lng]/partners/(partners)/documents/layout.tsx new file mode 100644 index 00000000..3ac0c573 --- /dev/null +++ b/app/[lng]/partners/(partners)/documents/layout.tsx @@ -0,0 +1,44 @@ + +import { cookies } from "next/headers" +import { Shell } from "@/components/shell" +import DocumentContainer from "@/components/documents/document-container" +import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services" +import { getVendorDocumentLists } from "@/lib/vendor-document/service" +import VendorDocumentsClient from "@/components/documents/vendor-docs.client" + + + +// Layout 컴포넌트는 서버 컴포넌트입니다 +export default async function VendorDocuments({ + children, +}: { + children: React.ReactNode +}) { + // const session = await getServerSession(authOptions) + // const vendorId = session?.user.companyId + const vendorId = "17" + const idAsNumber = Number(vendorId) + + const projects = await getVendorProjectsAndContracts(idAsNumber) + + + // 레이아웃 설정 쿠키 가져오기 + // Next.js 15에서는 cookies()가 Promise를 반환하므로 await 사용 + const cookieStore = await cookies() + + // 이제 cookieStore.get() 메서드 사용 가능 + const layout = cookieStore.get("react-resizable-panels:layout:mail") + const collapsed = cookieStore.get("react-resizable-panels:collapsed") + + const defaultLayout = layout ? JSON.parse(layout.value) : undefined + const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined + + + return ( + + + {children} + + + ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/documents/page.tsx b/app/[lng]/partners/(partners)/documents/page.tsx new file mode 100644 index 00000000..721eb408 --- /dev/null +++ b/app/[lng]/partners/(partners)/documents/page.tsx @@ -0,0 +1,21 @@ +// app/vendor-data/page.tsx +import * as React from "react" +import { Separator } from "@/components/ui/separator" + +export default async function IndexPage() { + return ( +
+ +
+
+

시작하는 방법

+

+ 오른쪽 상단에서 프로젝트/계약을 선택하세요.
+ + +

+
+
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/layout.tsx b/app/[lng]/partners/(partners)/layout.tsx new file mode 100644 index 00000000..9dc39f7b --- /dev/null +++ b/app/[lng]/partners/(partners)/layout.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from 'react'; +import { Header } from '@/components/layout/Header'; +import { SiteFooter } from '@/components/layout/Footer'; + +export default function EvcpLayout({ children }: { children: ReactNode }) { + return ( +
+
+
+
+ {children} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/rfq/page.tsx b/app/[lng]/partners/(partners)/rfq/page.tsx new file mode 100644 index 00000000..34b66115 --- /dev/null +++ b/app/[lng]/partners/(partners)/rfq/page.tsx @@ -0,0 +1,133 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsRfqsForVendorsCache } from "@/lib/rfqs/validations" +import { RfqsVendorTable } from "@/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { LogIn } from "lucide-react" +import { getRfqResponsesForVendor } from "@/lib/vendor-rfq-response/service" + +interface IndexPageProps { + searchParams: Promise +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsRfqsForVendorsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // Get session + const session = await getServerSession(authOptions) + + // Check if user is logged in + if (!session || !session.user) { + // Return login required UI instead of redirecting + return ( + +
+
+

+ RFQ +

+

+ RFQ를 응답하고 커뮤니케이션을 할 수 있습니다. +

+
+
+ +
+
+

로그인이 필요합니다

+

+ RFQ를 확인하려면 먼저 로그인하세요. +

+ +
+
+
+ ) + } + + // User is logged in, proceed with vendor ID + const vendorId = session.user.companyId + + // Validate vendorId (should be a number) + const idAsNumber = Number(vendorId) + + if (isNaN(idAsNumber)) { + // Handle invalid vendor ID (this shouldn't happen if authentication is working properly) + return ( + +
+
+

+ RFQ +

+
+
+
+
+

계정 오류

+

+ 업체 정보가 올바르게 설정되지 않았습니다. 관리자에게 문의하세요. +

+
+
+
+ ) + } + + // If we got here, we have a valid vendor ID + const promises = Promise.all([ + getRfqResponsesForVendor({ + ...search, + filters: validFilters, + }, idAsNumber) + ]) + + return ( + +
+
+
+

+ RFQ +

+

+ RFQ를 응답하고 커뮤니케이션을 할 수 있습니다. +

+
+
+
+ + }> + {/* DateRangePicker can go here */} + + + + } + > + + +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/system/page.tsx b/app/[lng]/partners/(partners)/system/page.tsx new file mode 100644 index 00000000..a1e9f8be --- /dev/null +++ b/app/[lng]/partners/(partners)/system/page.tsx @@ -0,0 +1,8 @@ + +export default function Pages() { + return ( + <> + test + + ) + } \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/tbe/page.tsx b/app/[lng]/partners/(partners)/tbe/page.tsx new file mode 100644 index 00000000..ab51659c --- /dev/null +++ b/app/[lng]/partners/(partners)/tbe/page.tsx @@ -0,0 +1,85 @@ +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { getTBEforVendor } from "@/lib/rfqs/service" +import { searchParamsTBECache } from "@/lib/rfqs/validations" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import { TbeVendorTable } from "@/lib/vendor-rfq-response/vendor-tbe-table/tbe-table" +import * as React from "react" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" + +interface IndexPageProps { + // Next.js 13 App Router에서 기본으로 주어지는 객체들 + params: { + lng: string + id: string + } + searchParams: Promise +} + +export default async function RfqTBEPage(props: IndexPageProps) { + const resolvedParams = await props.params + const lng = resolvedParams.lng + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsTBECache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const session = await getServerSession(authOptions) + const vendorId = session?.user.companyId + // const vendorId = "17" + + const idAsNumber = Number(vendorId) + + const promises = Promise.all([ + getTBEforVendor({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + + return ( + +
+
+
+

+ Technical Bid Evaluation +

+

+ TBE에 응답하고 커뮤니케이션을 할 수 있습니다.{" "} +

+
+
+
+ + }> + {/* */} + + + } + > + + +
+ ) +} diff --git a/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx new file mode 100644 index 00000000..248bd7fc --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx @@ -0,0 +1,41 @@ +import DynamicTable from "@/components/form-data/form-data-table" +import { getFormData } from "@/lib/forms/services" + +interface IndexPageProps { + params: { + lng: string + packageId: string + formId: string + } +} + +export default async function FormPage({ params }: IndexPageProps) { + // 1) 구조 분해 할당 + const resolvedParams = await params + + // 2) 구조 분해 할당 + const { lng, packageId, formId } = resolvedParams + + // 2) 변환 + const packageIdAsNumber = Number(packageId) + + // 3) DB 조회 + const { columns, data } = await getFormData(formId, packageIdAsNumber) + + // 4) 예외 처리 + if (!columns) { + return

해당 폼의 메타 정보를 불러올 수 없습니다.

+ } + + // 5) 렌더링 + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data/layout.tsx b/app/[lng]/partners/(partners)/vendor-data/layout.tsx new file mode 100644 index 00000000..a8b51c52 --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data/layout.tsx @@ -0,0 +1,69 @@ +// app/vendor-data/layout.tsx +import * as React from "react" +import { cookies } from "next/headers" +import { Shell } from "@/components/shell" +import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services" +import { VendorDataContainer } from "@/components/vendor-data/vendor-data-container" + +// Layout 컴포넌트는 서버 컴포넌트입니다 +export default async function VendorDataLayout({ + children, +}: { + children: React.ReactNode +}) { + // const session = await getServerSession(authOptions) + // const vendorId = session?.user.companyId + const vendorId = "17" + const idAsNumber = Number(vendorId) + + // 프로젝트 데이터 가져오기 + const projects = await getVendorProjectsAndContracts(idAsNumber) + + // 레이아웃 설정 쿠키 가져오기 + // Next.js 15에서는 cookies()가 Promise를 반환하므로 await 사용 + const cookieStore = await cookies() + + // 이제 cookieStore.get() 메서드 사용 가능 + const layout = cookieStore.get("react-resizable-panels:layout:mail") + const collapsed = cookieStore.get("react-resizable-panels:collapsed") + + const defaultLayout = layout ? JSON.parse(layout.value) : undefined + const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined + + return ( + +
+
+
+

+ Vendor Data +

+

+ 각종 Data 입력할 수 있습니다 +

+
+
+
+ +
+
+ {projects.length === 0 ? ( +
+ No projects found for this vendor. +
+ ) : ( + + {/* 페이지별 콘텐츠가 여기에 들어갑니다 */} + {children} + + )} +
+
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data/page.tsx b/app/[lng]/partners/(partners)/vendor-data/page.tsx new file mode 100644 index 00000000..3eead226 --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data/page.tsx @@ -0,0 +1,29 @@ +// app/vendor-data/page.tsx +import * as React from "react" +import { Separator } from "@/components/ui/separator" + +export default async function IndexPage() { + return ( +
+
+

벤더 데이터 대시보드

+

+ 왼쪽 사이드바에서 패키지를 선택하여 태그를 관리하세요. +

+
+ +
+
+

시작하는 방법

+

+ 1. 왼쪽 상단에서 프로젝트/계약을 선택하세요.
+ 2. 사이드바에서 패키지 항목을 클릭하세요.
+ 3. 선택한 패키지의 태그 정보를 확인하고 관리할 수 있습니다.
+ 4. 사이드바에서 폼 항목을 클릭하세요.
+ 5. 선택함 폼의 칼럼 정보를 확인하고 관리할 수 있습니다. +

+
+
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/(partners)/vendor-data/tag/[id]/page.tsx b/app/[lng]/partners/(partners)/vendor-data/tag/[id]/page.tsx new file mode 100644 index 00000000..7250732f --- /dev/null +++ b/app/[lng]/partners/(partners)/vendor-data/tag/[id]/page.tsx @@ -0,0 +1,43 @@ +import { Separator } from "@/components/ui/separator" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { TagsTable } from "@/lib/tags/table/tag-table" +import { searchParamsCache } from "@/lib/tags/validations" +import { getTags } from "@/lib/tags/service" + +interface IndexPageProps { + params: { + id: string + } + searchParams: Promise +} + +export default async function TagPage(props: IndexPageProps) { + const resolvedParams = await props.params + const id = resolvedParams.id + + const idAsNumber = Number(id) + + // 2) SearchParams 파싱 (Zod) + // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 + const searchParams = await props.searchParams + const search = searchParamsCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + const promises = Promise.all([ + getTags({ + ...search, + filters: validFilters, + }, + idAsNumber) + ]) + + // 4) 렌더링 + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/page.tsx b/app/[lng]/partners/page.tsx new file mode 100644 index 00000000..245d0228 --- /dev/null +++ b/app/[lng]/partners/page.tsx @@ -0,0 +1,21 @@ +import { Metadata } from "next" +import { LoginForm } from "@/components/login/login-form" +import { Suspense } from "react" +import { LoginFormSkeleton } from "@/components/login/login-form-skeleton" + +export const metadata: Metadata = { + title: "Partner Portal", + description: "", +} + +export default function AuthenticationPage() { + + + return ( + <> + }> + + + + ) +} diff --git a/app/[lng]/partners/pq/page.tsx b/app/[lng]/partners/pq/page.tsx new file mode 100644 index 00000000..8ad23f6e --- /dev/null +++ b/app/[lng]/partners/pq/page.tsx @@ -0,0 +1,39 @@ +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import * as React from "react" +import { Shell } from "@/components/shell" +import { Skeleton } from "@/components/ui/skeleton" +import { getPQDataByVendorId } from "@/lib/pq/service" +import { PQInputTabs } from "@/components/pq/pq-input-tabs" + + +export default async function PQInputPage() { + // 세션 + const session = await getServerSession(authOptions) + // 예: 세션에서 vendorId 가져오기 + // const vendorId = session?.user.companyId + const vendorId = 17 // 임시 + const idAsNumber = Number(vendorId) + + // 1) 서버에서 PQ 데이터 조회 (groupName별로 묶인 구조) + const pqData = await getPQDataByVendorId(idAsNumber) + + return ( + +
+

+ Pre-Qualification Check Sheet +

+

+ PQ에 적절한 응답을 제출하시기 바랍니다. 진행 중 문의가 있으면 담당자에게 연락바랍니다. +

+
+ + {/* 클라이언트 탭 UI 로드 (Suspense는 여기서는 크게 필요치 않을 수도 있음) */} + }> + + + +
+ ) +} \ No newline at end of file diff --git a/app/[lng]/partners/repository/page.tsx b/app/[lng]/partners/repository/page.tsx new file mode 100644 index 00000000..51c0fae5 --- /dev/null +++ b/app/[lng]/partners/repository/page.tsx @@ -0,0 +1,11 @@ +import { CompanyAuthForm } from "@/components/login/partner-auth-form" +import { Suspense } from "react" + +export default function RepositiryPage() { + return ( + Loading ...}> + + + + ) +} diff --git a/app/[lng]/partners/signup/page.tsx b/app/[lng]/partners/signup/page.tsx new file mode 100644 index 00000000..26c2944b --- /dev/null +++ b/app/[lng]/partners/signup/page.tsx @@ -0,0 +1,21 @@ +import { Suspense } from "react" +import { Metadata } from "next" +import { JoinForm } from "@/components/signup/join-form" +import { JoinFormSkeleton } from "@/components/signup/join-form-skeleton" + +// (Optional) If Next.js attempts to statically optimize this page and you need full runtime +// behavior for query params, you may also need: +// export const dynamic = "force-dynamic" + +export const metadata: Metadata = { + title: "Partner Portal", + description: "Authentication forms built using the components.", +} + +export default function SignUpPage() { + return ( + }> + + + ) +} \ No newline at end of file diff --git a/app/[lng]/qna/layout.tsx b/app/[lng]/qna/layout.tsx new file mode 100644 index 00000000..87651e92 --- /dev/null +++ b/app/[lng]/qna/layout.tsx @@ -0,0 +1,18 @@ +import { ReactNode } from 'react'; + + +export default function EvcpLayout({ children }: { children: ReactNode }) { + return ( +
+
+
+
+ {children} +
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/app/[lng]/qna/page.tsx b/app/[lng]/qna/page.tsx new file mode 100644 index 00000000..10280464 --- /dev/null +++ b/app/[lng]/qna/page.tsx @@ -0,0 +1,8 @@ + +export default function Pages() { + return ( + <> + qna + + ) + } \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..609a63d7 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,111 @@ +// (1) next-auth에서 필요한 타입들을 import +import NextAuth, { + NextAuthOptions, // authOptions에 쓸 타입 + Session, + User +} from 'next-auth' +import { JWT } from "next-auth/jwt" + +import CredentialsProvider from 'next-auth/providers/credentials' + +import { verifyOtp } from '@/lib/users/verifyOtp' + +// 1) 모듈 보강 선언 +declare module "next-auth" { + /** + * Session 객체를 확장 + */ + interface Session { + user: { + /** 우리가 필요로 하는 user id */ + id: string + + // 기본적으로 NextAuth가 제공하는 name/email/image 필드 + name?: string | null + email?: string | null + image?: string | null + companyId?: number | null + domain?: string | null + + } + } + + /** + * User 객체를 확장 + */ + interface User { + id: string + imageUrl?: string | null + companyId?: number | null + domain?: string | null + // 필요한 필드를 추가로 선언 가능 + } +} + +// (2) authOptions에 NextAuthOptions 타입 지정 +export const authOptions: NextAuthOptions = { + providers: [ + CredentialsProvider({ + name: 'Credentials', + credentials: { + email: { label: 'Email', type: 'text' }, + code: { label: 'OTP code', type: 'text' }, + }, + async authorize(credentials, req) { + const { email, code } = credentials ?? {} + + // OTP 검증 + const user = await verifyOtp(email ?? '', code ?? '') + if (!user) { + return null + } + + return { + id: String(user.id ?? email ?? "dts"), + email: user.email, + imageUrl: user.imageUrl ?? null, + name: user.name, // DB에서 가져온 실제 이름 + companyId: user.companyId, // DB에서 가져온 실제 이름 + domain: user.domain, // DB에서 가져온 실제 이름 + } + }, + }) + ], + // (3) session.strategy는 'jwt'가 되도록 선언 + // 필요하다면 as SessionStrategy 라고 명시해줄 수도 있음 + // 예) strategy: 'jwt' as SessionStrategy + session: { + strategy: 'jwt', + }, + callbacks: { + // (4) 콜백에서 token, user, session 등의 타입을 좀 더 명시해주고 싶다면 아래처럼 destructuring에 제네릭/타입 지정 + async jwt({ token, user }: { token: JWT; user?: User }) { + if (user) { + token.id = user.id + token.email = user.email + token.name = user.name + token.companyId = user.companyId + token.domain = user.domain + ; (token as any).imageUrl = (user as any).imageUrl + } + return token + }, + async session({ session, token }: { session: Session; token: JWT }) { + if (token) { + session.user = { + id: token.id as string, + email: token.email as string, + name: token.name as string, + domain: token.domain as string, + companyId: token.companyId as number, + image: (token as any).imageUrl ?? null + } + } + return session + }, + }, +} + +const handler = NextAuth(authOptions) + +export { handler as GET, handler as POST } \ No newline at end of file diff --git a/app/api/files/[...path]/route.ts b/app/api/files/[...path]/route.ts new file mode 100644 index 00000000..f92dd1d8 --- /dev/null +++ b/app/api/files/[...path]/route.ts @@ -0,0 +1,74 @@ +// app/api/files/[...path]/route.ts +import { NextRequest, NextResponse } from 'next/server' +import { readFile } from 'fs/promises' +import { join } from 'path' +import { stat } from 'fs/promises' + +export async function GET( + request: NextRequest, + { params }: { params: { path: string[] } } +) { + try { + + const path = request.nextUrl.searchParams.get("path"); + + + // 경로 파라미터에서 파일 경로 조합 + const filePath = join(process.cwd(), 'uploads', ...params.path) + + // 파일 존재 여부 확인 + try { + await stat(filePath) + } catch (error) { + return NextResponse.json( + { error: 'File not found' }, + { status: 404 } + ) + } + + // 파일 읽기 + const fileBuffer = await readFile(filePath) + + // 파일 확장자에 따른 MIME 타입 설정 + const fileName = params.path[params.path.length - 1] + const fileExtension = fileName.split('.').pop()?.toLowerCase() + + let contentType = 'application/octet-stream' + + if (fileExtension) { + const mimeTypes: Record = { + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'txt': 'text/plain', + 'csv': 'text/csv', + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + } + + contentType = mimeTypes[fileExtension] || contentType + } + + // 다운로드 설정 + const headers = new Headers() + headers.set('Content-Type', contentType) + headers.set('Content-Disposition', `attachment; filename="${fileName}"`) + + return new NextResponse(fileBuffer, { + status: 200, + headers, + }) + } catch (error) { + console.error('Error downloading file:', error) + return NextResponse.json( + { error: 'Failed to download file' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/app/api/rfq-download/route.ts b/app/api/rfq-download/route.ts new file mode 100644 index 00000000..19991128 --- /dev/null +++ b/app/api/rfq-download/route.ts @@ -0,0 +1,121 @@ +// app/api/rfq-download/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { readFile, access, constants } from 'fs/promises'; +import { join } from 'path'; +import db from '@/db/db'; +import { rfqAttachments } from '@/db/schema/rfq'; +import { eq } from 'drizzle-orm'; + +export async function GET(request: NextRequest) { + try { + // 파일 경로 파라미터 받기 + const path = request.nextUrl.searchParams.get("path"); + + if (!path) { + return NextResponse.json( + { error: "File path is required" }, + { status: 400 } + ); + } + + // DB에서 파일 정보 조회 (정확히 일치하는 filePath로 검색) + const [dbRecord] = await db + .select({ + fileName: rfqAttachments.fileName, + filePath: rfqAttachments.filePath + }) + .from(rfqAttachments) + .where(eq(rfqAttachments.filePath, path)); + + // 파일 정보 설정 + let fileName; + + if (dbRecord) { + // DB에서 찾은 경우 원본 파일명 사용 + fileName = dbRecord.fileName; + console.log("DB에서 원본 파일명 찾음:", fileName); + } else { + // DB에서 찾지 못한 경우 경로에서 파일명 추출 + fileName = path.split('/').pop() || 'download'; + } + + // 파일 경로 구성 + const storedPath = path.replace(/^\/+/, ""); // 앞쪽 슬래시 제거 + + // 파일 경로 시도 + const possiblePaths = [ + join(process.cwd(), "public", storedPath) + ]; + + // 실제 파일 찾기 + let actualPath = null; + for (const testPath of possiblePaths) { + try { + await access(testPath, constants.R_OK); + actualPath = testPath; + break; + } catch (err) { + console.log("❌ 경로에 파일 없음:", testPath); + } + } + + if (!actualPath) { + return NextResponse.json( + { + error: "File not found on server", + details: { + path: path, + triedPaths: possiblePaths + } + }, + { status: 404 } + ); + } + + const fileBuffer = await readFile(actualPath); + + // MIME 타입 결정 + const fileExtension = fileName.split('.').pop()?.toLowerCase() || ''; + + let contentType = 'application/octet-stream'; // 기본 바이너리 + + // 확장자에 따른 MIME 타입 매핑 + const mimeTypes: Record = { + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'txt': 'text/plain', + 'csv': 'text/csv', + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + }; + + contentType = mimeTypes[fileExtension] || contentType; + + // 다운로드용 헤더 설정 + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`); + headers.set('Content-Length', fileBuffer.length.toString()); + + return new NextResponse(fileBuffer, { + status: 200, + headers, + }); + } catch (error) { + console.error('❌ RFQ 파일 다운로드 오류:', error); + return NextResponse.json( + { + error: 'Failed to download file', + details: String(error) + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/rfq-upload/route.ts b/app/api/rfq-upload/route.ts new file mode 100644 index 00000000..97beafb1 --- /dev/null +++ b/app/api/rfq-upload/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server" +import { promises as fs } from "fs" +import path from "path" +import { v4 as uuidv4 } from "uuid" + +export async function POST(req: NextRequest) { + try { + const formData = await req.formData() + const file = formData.get("file") as File // "file" is default name from FilePond + if (!file) { + return NextResponse.json({ error: "No file" }, { status: 400 }) + } + + // e.g. parse a query param? or read 'rfqId' if we appended it + // const rfqId = ... (FilePond advanced config or handle differently) + + // read file + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + // unique filename + const uniqueName = uuidv4() + "-" + file.name + const targetDir = path.join(process.cwd(), "public", "rfq", "123") // or your rfqId + await fs.mkdir(targetDir, { recursive: true }) + const targetPath = path.join(targetDir, uniqueName) + + // write + await fs.writeFile(targetPath, buffer) + + // Return success. Typically you'd insert DB record here or return some reference + return NextResponse.json({ success: true, filePath: `/rfq/123/${uniqueName}` }) + } catch (error) { + console.error("upload error:", error) + return NextResponse.json({ error: String(error) }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/tbe-download/route.ts b/app/api/tbe-download/route.ts new file mode 100644 index 00000000..12e42920 --- /dev/null +++ b/app/api/tbe-download/route.ts @@ -0,0 +1,121 @@ +// app/api/rfq-download/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { readFile, access, constants } from 'fs/promises'; +import { join } from 'path'; +import db from '@/db/db'; +import { rfqAttachments, vendorResponseAttachments } from '@/db/schema/rfq'; +import { eq } from 'drizzle-orm'; + +export async function GET(request: NextRequest) { + try { + // 파일 경로 파라미터 받기 + const path = request.nextUrl.searchParams.get("path"); + + if (!path) { + return NextResponse.json( + { error: "File path is required" }, + { status: 400 } + ); + } + + // DB에서 파일 정보 조회 (정확히 일치하는 filePath로 검색) + const [dbRecord] = await db + .select({ + fileName: vendorResponseAttachments.fileName, + filePath: vendorResponseAttachments.filePath + }) + .from(vendorResponseAttachments) + .where(eq(vendorResponseAttachments.filePath, path)); + + // 파일 정보 설정 + let fileName; + + if (dbRecord) { + // DB에서 찾은 경우 원본 파일명 사용 + fileName = dbRecord.fileName; + console.log("DB에서 원본 파일명 찾음:", fileName); + } else { + // DB에서 찾지 못한 경우 경로에서 파일명 추출 + fileName = path.split('/').pop() || 'download'; + } + + // 파일 경로 구성 + const storedPath = path.replace(/^\/+/, ""); // 앞쪽 슬래시 제거 + + // 파일 경로 시도 + const possiblePaths = [ + join(process.cwd(), "public", storedPath) + ]; + + // 실제 파일 찾기 + let actualPath = null; + for (const testPath of possiblePaths) { + try { + await access(testPath, constants.R_OK); + actualPath = testPath; + break; + } catch (err) { + console.log("❌ 경로에 파일 없음:", testPath); + } + } + + if (!actualPath) { + return NextResponse.json( + { + error: "File not found on server", + details: { + path: path, + triedPaths: possiblePaths + } + }, + { status: 404 } + ); + } + + const fileBuffer = await readFile(actualPath); + + // MIME 타입 결정 + const fileExtension = fileName.split('.').pop()?.toLowerCase() || ''; + + let contentType = 'application/octet-stream'; // 기본 바이너리 + + // 확장자에 따른 MIME 타입 매핑 + const mimeTypes: Record = { + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'txt': 'text/plain', + 'csv': 'text/csv', + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + }; + + contentType = mimeTypes[fileExtension] || contentType; + + // 다운로드용 헤더 설정 + const headers = new Headers(); + headers.set('Content-Type', contentType); + headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`); + headers.set('Content-Length', fileBuffer.length.toString()); + + return new NextResponse(fileBuffer, { + status: 200, + headers, + }); + } catch (error) { + console.error('❌ RFQ 파일 다운로드 오류:', error); + return NextResponse.json( + { + error: 'Failed to download file', + details: String(error) + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/upload/route.ts b/app/api/upload/route.ts new file mode 100644 index 00000000..3b1d8be0 --- /dev/null +++ b/app/api/upload/route.ts @@ -0,0 +1,38 @@ +// app/api/upload/route.ts +import { NextRequest, NextResponse } from "next/server" +import { createWriteStream } from "fs" +import path from "path" +import { v4 as uuid } from "uuid" + +export async function POST(request: NextRequest) { + const formData = await request.formData() + const file = formData.get("file") as File | null + if (!file) { + return NextResponse.json({ error: "No file" }, { status: 400 }) + } + + // 여기서는 로컬 /public/uploads 에 저장한다고 가정 + const fileExt = path.extname(file.name) + const newFileName = `${uuid()}${fileExt}` + const filePath = path.join(process.cwd(), "public", "uploads", newFileName) + + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + // 로컬에 저장 + await new Promise((resolve, reject) => { + const writeStream = createWriteStream(filePath) + writeStream.write(buffer) + writeStream.end() + writeStream.on("finish", resolve) + writeStream.on("error", reject) + }) + + // /uploads/xxxx.ext 로 접근 가능 + const url = `/uploads/${newFileName}` + return NextResponse.json({ + fileName: file.name, + url, + size: file.size, + }) +} \ No newline at end of file diff --git a/app/api/vendors/attachments/download-temp/route.ts b/app/api/vendors/attachments/download-temp/route.ts new file mode 100644 index 00000000..987e421d --- /dev/null +++ b/app/api/vendors/attachments/download-temp/route.ts @@ -0,0 +1,102 @@ +// app/api/vendors/attachments/download-temp/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; +import { promises as fsPromises } from 'fs'; +import { cleanupTempFiles } from '@/lib/vendors/service'; + +export async function GET(request: NextRequest) { + try { + // 파일명 파라미터 추출 + const searchParams = request.nextUrl.searchParams; + const fileName = searchParams.get('file'); + + if (!fileName) { + return NextResponse.json( + { success: false, error: 'File name is required' }, + { status: 400 } + ); + } + + // 보안: 파일명에 경로 문자가 포함되어 있는지 확인 (경로 탐색 공격 방지) + if (fileName.includes('/') || fileName.includes('\\')) { + return NextResponse.json( + { success: false, error: 'Invalid file name' }, + { status: 400 } + ); + } + + // 임시 디렉토리의 파일 경로 생성 + const tempDir = path.join(process.cwd(), 'tmp'); + const filePath = path.join(tempDir, fileName); + + // 파일 존재 확인 + try { + await fsPromises.access(filePath, fs.constants.F_OK); + } catch { + return NextResponse.json( + { success: false, error: 'File not found' }, + { status: 404 } + ); + } + + // 파일 읽기 + const fileBuffer = await fsPromises.readFile(filePath); + + // 파일명에서 UUID 부분 제거하여 표시용 이름 생성 + const uuidPattern = /-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.zip$/i; + const displayName = fileName.replace(uuidPattern, '.zip'); + + // 파일 응답 반환 + const response = new NextResponse(fileBuffer, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': `attachment; filename="${encodeURIComponent(displayName)}"`, + }, + }); + + // 비동기적으로 파일 정리 요청 (별도 API 호출) + // Note: Next.js 환경에 따라 작동하지 않을 수 있음 + try { + fetch(`${request.nextUrl.origin}/api/vendors/cleanup-temp-files?file=${encodeURIComponent(fileName)}`, { + method: 'POST', + }).catch(e => console.error('임시 파일 정리 요청 실패:', e)); + } catch (e) { + console.error('파일 정리 요청 오류:', e); + } + + return response; + } catch (error) { + console.error('임시 파일 다운로드 오류:', error); + return NextResponse.json( + { success: false, error: 'Failed to download file' }, + { status: 500 } + ); + } +} + +// 임시 파일 정리 API 엔드포인트 +// app/api/vendors/cleanup-temp-files/route.ts +export async function POST(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const fileName = searchParams.get('file'); + + if (!fileName) { + return NextResponse.json({ success: false, error: 'File name is required' }, { status: 400 }); + } + + // 보안 검증 + if (fileName.includes('/') || fileName.includes('\\')) { + return NextResponse.json({ success: false, error: 'Invalid file name' }, { status: 400 }); + } + + // 서버 액션 호출하여 파일 정리 + await cleanupTempFiles(fileName); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('임시 파일 정리 API 오류:', error); + return NextResponse.json({ success: false, error: '임시 파일 정리 중 오류가 발생했습니다.' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/vendors/erp/route.ts b/app/api/vendors/erp/route.ts new file mode 100644 index 00000000..0724eeeb --- /dev/null +++ b/app/api/vendors/erp/route.ts @@ -0,0 +1,144 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { headers } from 'next/headers'; +import { getErrorMessage } from '@/lib/handle-error'; + +/** + * 기간계 시스템에 벤더 정보를 전송하는 API 엔드포인트 + * 서버 액션 내부에서 호출됨 + */ +export async function POST(request: NextRequest) { + try { + + // 요청 본문 파싱 + const vendorData = await request.json(); + + // 기간계 시스템 API 설정 + const erpApiUrl = process.env.ERP_API_URL; + const erpApiKey = process.env.ERP_API_KEY; + + if (!erpApiUrl || !erpApiKey) { + return NextResponse.json( + { success: false, message: 'ERP API configuration is missing' }, + { status: 500 } + ); + } + + // 기간계 시스템이 요구하는 형식으로 데이터 변환 + const erpRequestData = { + vendor: { + name: vendorData.vendorName, + tax_id: vendorData.taxId, + address: vendorData.address || "", + country: vendorData.country || "", + phone: vendorData.phone || "", + email: vendorData.email || "", + website: vendorData.website || "", + external_id: vendorData.id.toString(), + }, + contacts: vendorData.contacts.map((contact: any) => ({ + name: contact.contactName, + position: contact.contactPosition || "", + email: contact.contactEmail, + phone: contact.contactPhone || "", + is_primary: contact.isPrimary ? 1 : 0, + })), + items: vendorData.possibleItems.map((item: any) => ({ + item_code: item.itemCode, + description: item.description || "", + })), + attachments: vendorData.attachments.map((attach: any) => ({ + file_name: attach.fileName, + file_path: attach.filePath, + })), + }; + + // 기간계 시스템 API 호출 + const response = await fetch(erpApiUrl, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${erpApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(erpRequestData), + // Next.js의 fetch는 기본 30초 타임아웃 + }); + + // 응답 처리 + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return NextResponse.json( + { + success: false, + message: `ERP system error: ${response.status} ${response.statusText}`, + details: errorData + }, + { status: 502 } // Bad Gateway (외부 서버 오류) + ); + } + + const result = await response.json(); + + // 벤더 코드 검증 + if (!result.vendor_code) { + return NextResponse.json( + { success: false, message: 'Vendor code not provided in ERP response' }, + { status: 502 } + ); + } + + // 성공 응답 + return NextResponse.json({ + success: true, + vendorCode: result.vendor_code, + message: 'Vendor successfully registered in ERP system', + ...result + }); + } catch (error) { + console.error('Error in ERP API:', error); + return NextResponse.json( + { + success: false, + message: getErrorMessage(error) + }, + { status: 500 } + ); + } +} + +/** + * 기간계 시스템 연결 상태 확인 (헬스 체크) + */ +export async function GET() { + try { + const healthCheckUrl = process.env.ERP_HEALTH_CHECK_URL; + + if (!healthCheckUrl) { + return NextResponse.json( + { success: false, message: 'ERP health check URL not configured' }, + { status: 500 } + ); + } + + const response = await fetch(healthCheckUrl, { + method: 'GET', + next: { revalidate: 60 } // 1분마다 재검증 + }); + + const isAvailable = response.ok; + + return NextResponse.json({ + success: true, + available: isAvailable, + status: response.status, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('ERP health check error:', error); + return NextResponse.json({ + success: false, + available: false, + error: getErrorMessage(error), + timestamp: new Date().toISOString() + }); + } +} \ No newline at end of file diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 00000000..c427b92f --- /dev/null +++ b/app/globals.css @@ -0,0 +1,168 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --samsung: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + html { + @apply scroll-smooth; + } + body { + @apply bg-background text-foreground overscroll-none; + /* font-feature-settings: "rlig" 1, "calt" 1; */ + font-synthesis-weight: none; + text-rendering: optimizeLegibility; + } + + @supports (font: -apple-system-body) and (-webkit-appearance: none) { + [data-wrapper] { + @apply min-[1800px]:border-t; + } + } + /* Custom scrollbar styling. Thanks @pranathiperii. */ + ::-webkit-scrollbar { + width: 5px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background: hsl(var(--border)); + border-radius: 5px; + } + * { + scrollbar-width: thin; + scrollbar-color: hsl(var(--border)) transparent; + } +} + +@layer utilities { + .step { + counter-increment: step; + } + + .step:before { + @apply absolute w-9 h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 border-background; + @apply ml-[-50px] mt-[-4px]; + content: counter(step); + } + + .chunk-container { + @apply shadow-none; + } + + .chunk-container::after { + content: ""; + @apply absolute -inset-4 shadow-xl rounded-xl border; + } + + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + .border-grid { + @apply border-border/30 dark:border-border; + } + + .container-wrapper { + @apply min-[1800px]:max-w-[1536px] min-[1800px]:border-x border-border/30 dark:border-border mx-auto w-full; + } + + .container { + @apply px-4 xl:px-6 2xl:px-4 mx-auto max-w-[1536px]; + } +} + + +.MuiTreeItem-label{ + font-size: 0.875rem!important; +} + +.pdftron-container { + all: unset !important; +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 00000000..7ed768a5 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,85 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { languages } from "@/i18n/settings"; +import { Toaster } from "@/components/ui/toaster" +import { ThemeProvider } from "@/components/layout/providers"; +import { cn } from "@/lib/utils" +import { META_THEME_COLORS, siteConfig } from "@/config/site" +import { LicenseInfo } from '@mui/x-license'; +import { ToasterSonner } from "@/components/ui/toasterSonner"; + +LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_KEY as string); + +const inter = Inter({ subsets: ['latin'] }); + +export const generateStaticParams = async () => { + return languages.map((lng) => ({ lng })); +}; + +export const metadata: Metadata = { + title: { + default: siteConfig.name, + template: `%s - ${siteConfig.name}`, + }, + metadataBase: new URL(siteConfig.url), + description: siteConfig.description, + authors: [ + { + name: "DTS", + url: "https://dtsoution.io", + }, + ], + creator: "dujin", +}; + +export default async function RootLayout({ + children, + params: { lng }, +}: { + children: React.ReactNode; + params: { lng: string }; +}) { + + + return ( + + +