Compare commits

...

21 Commits

Author SHA1 Message Date
da0d77724d fix: correct layout interface and enhance user session management 2025-05-12 10:21:01 +09:00
c06a96bc1b Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-09 17:41:41 +09:00
8bc629698a feat: implement SurveyDetail page for roof-information
- 조사매물 지붕정보 상세페이지 구현
2025-05-09 17:41:26 +09:00
3e16d3a767 feat: implement session management with Zustand and enhance login flow in Login component 2025-05-09 16:05:20 +09:00
5e8d2fa8cc feat: enhance DoubleBtnAlert component with dynamic alert messages and button actions 2025-05-09 15:19:03 +09:00
436f149635 feat: implement alert management in EdgeProvider with dynamic alert types 2025-05-09 15:17:19 +09:00
34638ff0f0 fix: Change prisma db URL 2025-05-09 15:16:58 +09:00
65483812a6 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-09 15:11:54 +09:00
95d971b198 feat: remove unused survey sale API routes & add address popup at Survey BasicForm 2025-05-09 15:08:55 +09:00
a6702f52fc feat: add new sample page with various input components and styling for enhanced user interaction 2025-05-09 14:44:07 +09:00
06967d746c refactor: streamline MS_SUITABLE model by removing redundant fields and add new BC_COMM_H and BC_COMM_L models for improved data structure 2025-05-09 13:53:33 +09:00
b4db26d80a fix: update EdgeProvider to correctly set back button visibility based on pathname 2025-05-08 18:00:57 +09:00
4ab2b225cd feat: add new compliance-related SVG icons for enhanced UI representation 2025-05-08 17:57:48 +09:00
5628d330d5 fix: update button action in Main component to navigate to suitable page for roof material compatibility checks 2025-05-08 17:56:35 +09:00
1746c91742 feat: integrate EdgeProvider for alert management and add Suitable components for roof material compatibility checks 2025-05-08 17:55:24 +09:00
4467c04321 style: update SCSS styles for various components, adding responsive design adjustments and new styles for compliance checks 2025-05-08 17:55:10 +09:00
555e6f3b4a Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-05-08 17:02:42 +09:00
2397b7f144 feat: add survey roof-info validation when Create, Update
- 조사 매물 생성, 수정 시 각 컬럼 필수값 validation 추가
- 조사 매물 수정 페이지에서 기타 옵션 선택 시 값 초기화 되도록 구현
2025-05-08 17:01:10 +09:00
dfd5ba419b feat: implement SurveyFilteringOptions to global
- 조사매물 검색 조건 zustand 통해 전역으로 관리하도록 구현
2025-05-08 16:58:14 +09:00
0d12239460 refactor: expand MS_SUITABLE model in Prisma schema with detailed field definitions and comments for clarity 2025-05-08 15:47:29 +09:00
e355ad5378 refactor: clean up User and MS_SUITABLE models in Prisma schema by removing unnecessary comments and optimizing field definitions 2025-05-08 15:41:12 +09:00
65 changed files with 2143 additions and 650 deletions

4
.env
View File

@ -5,8 +5,8 @@
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
# DATABASE_URL="sqlserver://3team.devgrr.kr:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;"
# DATABASE_URL="sqlserver://3team.devgrr.kr:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;"
DATABASE_URL="mysql://root:root@localhost:3306/onsitesurvey"
DATABASE_URL="sqlserver://3team.devgrr.kr:1433;database=onsitesurvey;user=sa;password=1q2w3e4r!;encrypt=true;trustServerCertificate=true;"
# DATABASE_URL="mysql://root:root@localhost:3306/onsitesurvey"
# SESSION_PASSWORD="QWERASDFZXCV1234567890REWQFDSAVCXZ"
SESSION_PASSWORD="This application is for mobile field research"

View File

@ -12,10 +12,10 @@ const nextConfig: NextConfig = {
source: '/api/user/login',
destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`,
},
{
source: '/api/:path*',
destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,
},
// {
// source: '/api/:path*',
// destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`,
// },
]
},
}

View File

@ -3,11 +3,10 @@ generator client {
}
datasource db {
provider = "mysql"
provider = "sqlserver"
url = env("DATABASE_URL")
}
// 사용자 정보
model User {
id Int @id @default(autoincrement())
username String @unique
@ -20,186 +19,161 @@ model User {
updated_at DateTime @updatedAt
}
// 지붕재 적합성 정보
model MS_SUITABLE {
//일련번호
id Int @id @default(autoincrement())
//제품명
product_name String @db.VarChar(200)
//제조업체명
manufacturer String? @db.VarChar(200)
//지붕재
roof_material String? @db.VarChar(100)
//금구형태(쇠붙이형)
shape String? @db.VarChar(200)
//지지 기와
support_roof_tile String? @db.VarChar(1)
//지지 기와 메모
support_roof_tile String? @db.VarChar(2)
support_roof_tile_memo String? @db.VarChar(500)
//지지 금구
support_roof_bracket String? @db.VarChar(200)
//지지 금구 메모
support_roof_bracket_memo String? @db.VarChar(500)
//yg 앵커
yg_anchor String? @db.VarChar(200)
//yg 앵커 메모
yg_anchor_memo String? @db.VarChar(500)
//rg 지붕판
rg_roof_tile_part String? @db.VarChar(200)
//rg 지붕판 메모
rg_roof_tile_part_memo String? @db.VarChar(500)
//다이도헌트 지지 기와2
dido_hunt_support_tile_2 String? @db.VarChar(200)
//다이도헌트 지지 기와2 메모
dido_hunt_support_tile_2_memo String? @db.VarChar(500)
//타카시마 파워 베이스
takashima_power_base String? @db.VarChar(200)
//타카시마 파워 베이스 메모
takashima_power_base_memo String? @db.VarChar(500)
//타카시마용 금구
takashima_tile_bracket String? @db.VarChar(200)
//타카시마용 금구 메모
takashima_tile_bracket_memo String? @db.VarChar(500)
//슬레이트 금구4
slate_bracket_4 String? @db.VarChar(200)
//슬레이트 금구4 메모
slate_bracket_4_memo String? @db.VarChar(500)
//슬레이트 판금 금구(슬레이트, 싱글)
slate_single_metal_bracket String? @db.VarChar(200)
//슬레이트 판금 금구 메모(슬레이트, 싱글)
slate_single_metal_bracket_memo String? @db.VarChar(500)
//다이도헌트 짧은 트랙4
dido_hunt_short_rack_4 String? @db.VarChar(200)
//다이도헌트 짧은 트랙4 메모
dido_hunt_short_rack_4_memo String? @db.VarChar(500)
//타카시마 슬레이트 금구
takashima_slate_bracket_slate_single String? @db.VarChar(200)
//타카시마 슬레이트 금구 메모
takashima_slate_bracket_slate_single_memo String? @db.VarChar(500)
//df 판금 금구
df_metal_bracket String? @db.VarChar(200)
//df 판금 금구 메모
df_metal_bracket_memo String? @db.VarChar(500)
//슬레이트 판금 금구(금속 지붕)
slate_metal_bracket String? @db.VarChar(200)
//슬레이트 판금 금구(금속 지붕) 메모
slate_metal_bracket_memo String? @db.VarChar(500)
//타카시마 슬레이트 금구(금속 지붕)
takashima_slate_bracket_metal_roof String? @db.VarChar(200)
//타카시마 슬레이트 금구(금속 지붕) 메모
takashima_slate_bracket_metal_roof_memo String? @db.VarChar(500)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
// 조사 매물 기본 정보
model SD_SERVEY_SALES_BASIC_INFO {
//일련번호
id Int @id @default(autoincrement())
//담당자명
representative String @db.VarChar(200)
//판매점
store String? @db.VarChar(200)
//시공점
construction_point String? @db.VarChar(200)
//현재 조사일
investigation_date String? @db.VarChar(10)
//건물명
building_name String? @db.VarChar(200)
//고객명
customer_name String? @db.VarChar(200)
//우편번호
post_code String? @db.VarChar(10)
//주소
address String? @db.VarChar(200)
//상세주소
address_detail String? @db.VarChar(300)
//제출상태
submission_status Boolean @default(false)
//제출일
submission_date DateTime? @db.Date
//상세정보
detail_info SD_SERVEY_SALES_DETAIL_INFO?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
detail_info SD_SERVEY_SALES_DETAIL_INFO?
}
// 조사 매물 전기 지붕 정보
model SD_SERVEY_SALES_DETAIL_INFO {
//일련번호
id Int @id @default(autoincrement())
//전기계약 용량
contract_capacity String? @db.VarChar(20)
//전기 소매 회사
retail_company String? @db.VarChar(100)
//전기 부대 설비
supplementary_facilities String? @db.VarChar(20)
//전기 부대 설비 기타
supplementary_facilities_etc String? @db.VarChar(200)
//설치 희망 시스템
installation_system Int? @db.Int
//설치 희망 시스템 기타
installation_system Int?
installation_system_etc String? @db.VarChar(200)
//건축 연수
construction_year Int? @db.Int
//건축 연수 기타
construction_year Int?
construction_year_etc String? @db.VarChar(200)
//지붕재
roof_material String? @db.VarChar(20)
//지붕재 기타
roof_material_etc String? @db.VarChar(200)
//지붕 모양
roof_shape Int? @db.Int
//지붕 모양 기타
roof_shape Int?
roof_shape_etc String? @db.VarChar(200)
//지붕 경사도
roof_slope String? @db.VarChar(5)
//주택 구조
house_structure Int? @db.Int
//주택 구조 기타
house_structure Int?
house_structure_etc String? @db.VarChar(200)
//서까래 재질
rafter_material Int? @db.Int
//서까래 재질 기타
rafter_material Int?
rafter_material_etc String? @db.VarChar(200)
//서까래 크기
rafter_size Int? @db.Int
//서까래 크기 기타
rafter_size Int?
rafter_size_etc String? @db.VarChar(200)
//서까래 피치
rafter_pitch Int? @db.Int
//서까래 피치 기타
rafter_pitch Int?
rafter_pitch_etc String? @db.VarChar(200)
//서까래 방향
rafter_direction Int? @db.Int
//노지판 종류
open_field_plate_kind Int? @db.Int
//노지판 종류 기타
rafter_direction Int?
open_field_plate_kind Int?
open_field_plate_kind_etc String? @db.VarChar(200)
//노지판 두께
open_field_plate_thickness String? @db.VarChar(5)
//누수 흔적
leak_trace Boolean? @default(false)
//방수재 종류
waterproof_material Int? @db.Int
//방수재 종류 기타
waterproof_material Int?
waterproof_material_etc String? @db.VarChar(200)
//단열재 여부
insulation_presence Int? @db.Int
//단열재 여부 기타
insulation_presence Int?
insulation_presence_etc String? @db.VarChar(200)
//지붕 구조 순서
structure_order Int? @db.Int
//지붕 구조 순서 기타
structure_order Int?
structure_order_etc String? @db.VarChar(200)
//설치 가능 여부
installation_availability Int? @db.Int
//설치 가능 여부 기타
installation_availability Int?
installation_availability_etc String? @db.VarChar(200)
//메모
memo String? @db.VarChar(500)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id])
basic_info_id Int @unique
basic_info SD_SERVEY_SALES_BASIC_INFO @relation(fields: [basic_info_id], references: [id])
}
model BC_COMM_H {
HEAD_CD String @id(map: "PK_BC_COMM_H") @db.NVarChar(6)
HEAD_ID String @db.NVarChar(100)
HEAD_NM String @db.NVarChar(100)
HEAD_JP String @db.NVarChar(100)
HEAD_4TH String @db.NVarChar(100)
REF_CHR1 String @db.NVarChar(100)
REF_CHR2 String @db.NVarChar(100)
REF_CHR3 String @db.NVarChar(100)
REF_CHR4 String @db.NVarChar(100)
REF_CHR5 String @db.NVarChar(100)
REF_NUM1 String @db.NVarChar(100)
REF_NUM2 String @db.NVarChar(100)
REF_NUM3 String @db.NVarChar(100)
REF_NUM4 String @db.NVarChar(100)
REF_NUM5 String @db.NVarChar(100)
REMARKS String @db.NVarChar(200)
SAP_YN String @db.NVarChar(1)
STAT_CD String @db.NVarChar(1)
DEL_YN String @db.NVarChar(1)
REG_DT DateTime? @db.DateTime
REG_ID String @db.NVarChar(50)
UPT_DT DateTime? @db.DateTime
UPT_ID String @db.NVarChar(50)
QC_COMM_YN String? @default("N", map: "DF__BC_COMM_H__QC_CO__48CFD27E") @db.NVarChar(1)
BC_COMM_L BC_COMM_L[]
@@index([HEAD_ID], map: "BC_COMM_H_HEAD_ID_IDX")
}
model BC_COMM_L {
HEAD_CD String @db.NVarChar(6)
CODE String @db.NVarChar(50)
READ_CD String? @db.NVarChar(50)
CODE_NM String? @db.NVarChar(100)
CODE_JP String? @db.NVarChar(100)
CODE_4TH String? @db.NVarChar(100)
REF_CHR1 String? @db.NVarChar(150)
REF_CHR2 String? @db.NVarChar(150)
REF_CHR3 String? @db.NVarChar(150)
REF_CHR4 String? @db.NVarChar(150)
REF_CHR5 String? @db.NVarChar(150)
REF_NUM1 Decimal? @db.Decimal(22, 5)
REF_NUM2 Decimal? @db.Decimal(22, 5)
REF_NUM3 Decimal? @db.Decimal(22, 5)
REF_NUM4 Decimal? @db.Decimal(22, 5)
REF_NUM5 Decimal? @db.Decimal(22, 5)
PRIORITY Decimal? @db.Decimal(3, 0)
REF_CNT String? @db.NVarChar(5)
STAT_CD String? @db.NVarChar(1)
DEL_YN String? @db.NVarChar(1)
REG_DT DateTime? @db.DateTime
REG_ID String? @db.NVarChar(50)
UPT_DT DateTime? @db.DateTime
UPT_ID String? @db.NVarChar(50)
BC_COMM_H BC_COMM_H @relation(fields: [HEAD_CD], references: [HEAD_CD], onUpdate: NoAction, map: "FK_BC_COMM_L")
@@id([HEAD_CD, CODE], map: "PK_BC_COMM_L")
}

View File

@ -0,0 +1,5 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 2.00372C10.6049 1.99039 9.7047 2.01289 8.8294 2.07107C4.64639 2.34913 1.31441 5.72838 1.04024 9.97072C0.986587 10.8009 0.986587 11.6607 1.04024 12.4909C1.1401 14.036 1.82343 15.4666 2.62791 16.6746C3.09501 17.5203 2.78674 18.5758 2.30021 19.4978C1.94941 20.1626 1.77401 20.495 1.91484 20.7351C2.05568 20.9752 2.37026 20.9829 2.99943 20.9982C4.24367 21.0285 5.08268 20.6757 5.74868 20.1846C6.1264 19.9061 6.31527 19.7668 6.44544 19.7508C6.5756 19.7348 6.83177 19.8403 7.34401 20.0513C7.8044 20.2409 8.33896 20.3579 8.8294 20.3905C10.2536 20.4852 11.7435 20.4854 13.1706 20.3905C17.3536 20.1125 20.6856 16.7332 20.9598 12.4909C21.0021 11.836 21.011 11.1627 20.9866 10.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 14H14.5M7.5 9H11" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 4.5H21M17.5 1L17.5 8" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="21" height="21" rx="10.5" stroke="#A8B6C7"/>
<path d="M7 13L11 9L15 13" stroke="#A8B6C7" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C15.9706 20 20 15.9706 20 11Z" fill="#F1FFF4" stroke="#6CB67C"/>
<path d="M7 11.5875L9.5625 14.15L15.2 8" stroke="#6CB67C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C15.9706 20 20 15.9706 20 11Z" fill="#EFF7FF" stroke="#67A9EF"/>
<path d="M10.9219 14.7871C11.3002 14.8253 11.5956 15.1448 11.5957 15.5332C11.5957 15.9217 11.3002 16.2411 10.9219 16.2793L10.8457 16.2832H10.833C10.419 16.283 10.083 15.9473 10.083 15.5332C10.0831 15.1192 10.4191 14.7834 10.833 14.7832H10.8457L10.9219 14.7871ZM10.835 5C12.4218 5.00022 13.6687 6.32107 13.6689 7.90039C13.6689 8.4001 13.545 8.87321 13.3252 9.28613L13.2256 9.45898C13.0473 9.74483 12.8377 10.0187 12.6436 10.2676C12.4417 10.5263 12.2576 10.7567 12.0908 10.9932C11.7534 11.4716 11.5851 11.8465 11.585 12.2002V12.7383C11.5847 13.1522 11.2489 13.4881 10.835 13.4883C10.4209 13.4883 10.0852 13.1523 10.085 12.7383V12.2002C10.0851 11.3669 10.4817 10.6716 10.8652 10.1279C11.059 9.85327 11.2756 9.58331 11.4609 9.3457C11.6538 9.09844 11.8192 8.87814 11.9521 8.66504L12.0439 8.49316C12.1238 8.3143 12.1689 8.11368 12.1689 7.90039C12.1687 7.10541 11.55 6.50023 10.835 6.5C10.1198 6.50002 9.50021 7.10528 9.5 7.90039C9.49985 8.31448 9.16412 8.65039 8.75 8.65039C8.33589 8.65037 8.00015 8.31447 8 7.90039C8.00021 6.32095 9.24791 5.00002 10.835 5Z" fill="#67A9EF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C15.9706 20 20 15.9706 20 11Z" fill="#F6F6F6" stroke="#999999"/>
<path d="M11.0088 13.75C11.5611 13.75 12.0088 14.1977 12.0088 14.75C12.0088 15.3023 11.5611 15.75 11.0088 15.75H11C10.4477 15.75 10 15.3023 10 14.75C10 14.1977 10.4477 13.75 11 13.75H11.0088ZM11 6C11.4142 6 11.75 6.33579 11.75 6.75V11.75C11.75 12.1642 11.4142 12.5 11 12.5C10.5858 12.5 10.25 12.1642 10.25 11.75V6.75C10.25 6.33579 10.5858 6 11 6Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@ -0,0 +1,4 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C15.9706 20 20 15.9706 20 11Z" fill="#FFF3F7" stroke="#EB6F94"/>
<path d="M8 8L11 11M11 11L14 14M11 11L14 8M11 11L8 14" stroke="#EB6F94" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 401 B

View File

@ -14,7 +14,7 @@ export async function POST(request: Request) {
loginId,
pwd,
})
console.log('🚀 ~ result ~ result:', result)
// console.log('🚀 ~ result ~ result:', result)
if (result.data.result.code === 200) {
const cookieStore = await cookies()
@ -54,5 +54,5 @@ export async function POST(request: Request) {
await session.save()
}
return NextResponse.json({ code: 200, message: 'Login is Succecss!!' })
return NextResponse.json({ code: 200, message: 'Login is Succecss!!', result: result.data.data })
}

View File

@ -1,72 +0,0 @@
import { NextResponse } from 'next/server'
export async function POST(request: Request, context: { params: { id: string } }) {
const body = await request.json()
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
export async function GET(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.findUnique({
where: { id: Number(id) },
include: {
detail_info: true,
},
})
return NextResponse.json(survey)
}
export async function PUT(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
const body = await request.json()
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
...body,
detail_info: {
update: body.detail_info,
},
},
})
return NextResponse.json(survey)
}
export async function DELETE(request: Request, context: { params: { id: string; detail_id: string } }) {
const { id, detail_id } = await context.params
if (detail_id) {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_DETAIL_INFO.delete({
where: { id: Number(detail_id) },
})
} else {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.delete({
where: { id: Number(id) },
})
}
return NextResponse.json({ message: 'Survey deleted successfully' })
}
export async function PATCH(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
}

View File

@ -1,33 +0,0 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma'
export async function POST(request: Request) {
const body = await request.json()
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.create({
data: body,
})
return NextResponse.json(res)
}
export async function GET() {
try {
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany()
return NextResponse.json(res)
} catch (error) {
console.error(error)
}
}
export async function PUT(request: Request) {
const body = await request.json()
console.log('🚀 ~ PUT ~ body:', body)
const detailInfo = { ...body.detail_info, basic_info_id: body.id }
console.log('🚀 ~ PUT ~ detailInfo:', detailInfo)
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_DETAIL_INFO.create({
data: detailInfo,
})
return NextResponse.json({ message: 'Survey sales updated successfully' })
}

View File

@ -1,21 +1,5 @@
import { NextResponse } from 'next/server'
export async function POST(request: Request, context: { params: { id: string } }) {
const body = await request.json()
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
export async function GET(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
@ -80,12 +64,45 @@ export async function DELETE(request: Request, context: { params: { id: string }
export async function PATCH(request: Request, context: { params: { id: string } }) {
const { id } = await context.params
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
const body = await request.json()
if (body.submit) {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
submission_status: true,
submission_date: new Date(),
},
})
return NextResponse.json({ message: 'Survey confirmed successfully' })
} else {
// @ts-ignore
const hasDetails = await prisma.SD_SERVEY_SALES_DETAIL_INFO.findUnique({
where: { basic_info_id: Number(id) },
})
if (hasDetails) {
//@ts-ignore
const result = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
updated_at: new Date(),
detail_info: {
update: body.detail_info,
},
},
})
return NextResponse.json(result)
} else {
// @ts-ignore
const survey = await prisma.SD_SERVEY_SALES_BASIC_INFO.update({
where: { id: Number(id) },
data: {
detail_info: {
create: body.detail_info,
},
},
})
return NextResponse.json({ message: 'Survey detail created successfully' })
}
}
}

View File

@ -10,32 +10,75 @@ export async function POST(request: Request) {
return NextResponse.json(res)
}
export async function GET() {
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const keyword = searchParams.get('keyword')
const searchOption = searchParams.get('searchOption')
const isMySurvey = searchParams.get('isMySurvey')
const sort = searchParams.get('sort')
const offset = searchParams.get('offset')
const searchOptions = ['building_name', 'representative', 'store', 'construction_point', 'customer_name', 'post_code', 'address', 'address_detail']
try {
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany()
return NextResponse.json(res)
const where: any = {}
if (isMySurvey !== null && isMySurvey !== undefined) {
where.representative = isMySurvey
}
if (keyword && keyword.trim() !== '' && searchOption) {
if (searchOption === 'all') {
where.OR = []
if (keyword.match(/^\d+$/) || !isNaN(Number(keyword))) {
where.OR.push({
id: {
equals: Number(keyword),
},
})
}
where.OR.push(
...searchOptions.map((field) => ({
[field]: {
contains: keyword,
},
})),
)
} else if (searchOptions.includes(searchOption)) {
where[searchOption] = {
contains: keyword,
}
} else if (searchOption === 'id') {
where[searchOption] = {
equals: Number(keyword),
}
}
}
if (offset) {
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({
where,
orderBy: sort === 'created' ? { created_at: 'desc' } : { updated_at: 'desc' },
skip: Number(offset),
take: 10,
})
return NextResponse.json(res)
} else {
// @ts-ignore
const count = await prisma.SD_SERVEY_SALES_BASIC_INFO.count({
where,
})
return NextResponse.json(count)
}
} catch (error) {
console.error(error)
throw error
}
}
// export async function GET(request: Request) {
// // @ts-ignore
// const res = await prisma.SD_SERVEY_SALES_BASIC_INFO.findMany({
// include: {
// detail_info: true,
// },
// })
// return NextResponse.json(res)
// }
export async function PUT(request: Request) {
const body = await request.json()
console.log('🚀 ~ PUT ~ body:', body)
const detailInfo = { ...body.detail_info, basic_info_id: body.id }
console.log('🚀 ~ PUT ~ detailInfo:', detailInfo)
// @ts-ignore
const res = await prisma.SD_SERVEY_SALES_DETAIL_INFO.create({
data: detailInfo,

View File

@ -2,7 +2,8 @@ import { NextResponse } from 'next/server'
import { prisma } from '@/libs/prisma'
import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers'
import { SessionData, sessionOptions } from '@/libs/session'
import { sessionOptions } from '@/libs/session'
import type { SessionData } from '@/types/Auth'
export async function POST(request: Request) {
const { username, password } = await request.json()
@ -25,7 +26,7 @@ export async function POST(request: Request) {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
console.log('start session edit!')
session.username = user.username!
session.userNm = user.username!
session.email = user.email!
session.isLoggedIn = true
console.log('end session edit!')

View File

@ -1,9 +0,0 @@
import InquiryWriteForm from '@/components/inquiry/InquiryWriteForm'
export default function InquiryWrite() {
return (
<div>
<InquiryWriteForm />
</div>
)
}

View File

@ -1,6 +1,7 @@
import type { Metadata } from 'next'
import ReactQueryProviders from '@/providers/ReactQueryProvider'
import EdgeProvider from '@/providers/EdgeProvider'
import PopupController from '@/components/ui/PopupController'
import '@/styles/style.scss'
@ -17,22 +18,24 @@ interface RootLayoutProps {
header: ReactNode
footer: ReactNode
floatBtn: ReactNode
}
}6
export default async function RootLayout({ children, header, footer, floatBtn }: RootLayoutProps): Promise<ReactNode> {
return (
<ReactQueryProviders>
<html lang="en">
<body>
<div className="wrap">
{header}
{children}
{footer}
{floatBtn}
</div>
<PopupController />
</body>
</html>
<EdgeProvider>
<html lang="ja" suppressHydrationWarning>
<body>
<div className="wrap">
{header}
{children}
{footer}
{floatBtn}
</div>
<PopupController />
</body>
</html>
</EdgeProvider>
</ReactQueryProviders>
)
}

240
src/app/sample/page.tsx Normal file
View File

@ -0,0 +1,240 @@
'use client'
import { useState } from 'react'
export default function page() {
const [fileName, setFileName] = useState<File | null>(null) //file name value
return (
<>
<div className="form-design-wrap">
<div className="design-box">
<h1 className="tit">Input</h1>
<div className="design-grid-wrap">
<input className="input-frame" type="text" placeholder="Input Frame Text" />
<input className="input-frame" type="number" placeholder="Input Frame Number" />
<input className="input-frame" type="password" placeholder="Input Frame Password" />
<input className="input-frame" type="email" placeholder="Input Frame Email" />
<input className="input-frame" type="text" placeholder="Input Frame Disabled" defaultValue={'defaultValue'} disabled />
<input className="input-frame" type="text" placeholder="Input Frame ReadOnly" readOnly />
<div className="filebox">
<label className="btn-frame l-blue icon" htmlFor="file">
<i className="btn-clip"></i>Upload File
</label>
<input type="file" id="file" onChange={(e) => setFileName(e.target.files?.[0] ?? null)} />
</div>
<div className="search-input">
<input type="text" className="search-frame" placeholder="Input Frame Search" />
<button className="search-icon"></button>
</div>
<div className="date-input">
<button className="date-btn">
<i className="date-icon"></i>
</button>
<input type="text" className="date-frame" placeholder="Input Frame Date" />
</div>
<div className="login-input id">
<input type="text" className="login-frame" placeholder="Input Frame ID" />
<button className="login-icon">
<i className="del-icon"></i>
</button>
</div>
<div className="login-input pw">
<input type="password" className="login-frame" placeholder="Input Frame PW" />
<button className="login-icon act">
<i className="show-icon"></i>
</button>
</div>
<div className="login-input pw change">
<input type="password" className="login-frame" placeholder="Input Frame PW" />
<button className="login-icon act">
<i className="show-icon"></i>
</button>
</div>
</div>
</div>
<div className="design-box">
<h1 className="tit">Button</h1>
<div className="design-grid-wrap">
<button className="btn-frame d-blue min">Block Button</button>
<button className="btn-frame n-blue min">Block Button</button>
<button className="btn-frame red min">Block Button</button>
<button className="btn-frame d-blue">Block Button</button>
<button className="btn-frame n-blue">Block Button</button>
<button className="btn-frame l-blue">Block Button</button>
<button className="btn-frame red">Block Button</button>
<button className="btn-frame d-blue icon">
Block Icon Button<i className="btn-arr"></i>
</button>
<button className="btn-frame n-blue icon">
Block Icon Button<i className="btn-arr"></i>
</button>
<button className="btn-frame n-blue icon">
Block Icon Button<i className="btn-edit"></i>
</button>
<button className="btn-frame red icon">
Block Icon Button<i className="btn-arr"></i>
</button>
<button className="btn-frame l-blue icon">
<i className="btn-clip"></i>Block Icon Button
</button>
<button className="btn-frame icon login">
Block Big Button <i className="btn-arr"></i>
</button>
<button className="btn-frame" disabled>
Block Button
</button>
</div>
</div>
<div className="design-box">
<h1 className="tit">Check Box</h1>
<div className="design-grid-wrap">
<div className="check-form-box">
<input type="checkbox" id="ch01" />
<label htmlFor="ch01">Check Box</label>
</div>
<div className="check-form-box">
<input type="checkbox" id="ch02" />
<label htmlFor="ch02">Check Box</label>
</div>
<div className="check-form-box">
<input type="checkbox" id="ch03" defaultChecked />
<label htmlFor="ch03">Check Box</label>
</div>
<div className="check-form-box">
<input type="checkbox" id="ch04" defaultChecked />
<label htmlFor="ch04">Check Box</label>
</div>
<div className="check-form-box">
<input type="checkbox" id="ch05" disabled />
<label htmlFor="ch05">Check Box</label>
</div>
<div className="check-form-box">
<input type="checkbox" id="ch06" disabled />
<label htmlFor="ch06">Check Box</label>
</div>
</div>
</div>
<div className="design-box">
<h1 className="tit">Radio Button</h1>
<div className="design-grid-wrap">
<div className="radio-form-box">
<input type="radio" name="radio01" id="ra01" />
<label htmlFor="ra01">Radio Button</label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio01" id="ra02" defaultChecked />
<label htmlFor="ra02">Radio Button</label>
</div>
<div className="radio-form-box">
<input type="radio" name="radio03" id="ra05" disabled />
<label htmlFor="ra05">Radio Button</label>
</div>
</div>
</div>
<div className="design-box">
<h1 className="tit">Toggle Button</h1>
<div className="design-grid-wrap">
<div>
<label className="toggle-btn">
<input type="checkbox" />
<span className="slider"></span>
</label>
</div>
<div className="toggle-form">
<label className="toggle-btn">
<input type="checkbox" />
<span className="slider"></span>
</label>
<div className="toggle-name">Q.PARTNERS</div>
</div>
</div>
</div>
<div className="design-box">
<h1 className="tit">Select Box</h1>
<div className="design-grid-wrap">
<select className="select-form" name="" id="">
<option value="">Select01</option>
<option value="">Select02</option>
<option value="">Select03</option>
<option value="">Select04</option>
<option value="">Select05</option>
</select>
<select className="select-form" name="" id="" disabled>
<option value="">Select01</option>
<option value="">Select02</option>
<option value="">Select03</option>
<option value="">Select04</option>
<option value="">Select05</option>
</select>
<div className="select-flex-form">
<select className="select-form" name="" id="">
<option value="">Select01</option>
<option value="">Select02</option>
<option value="">Select03</option>
<option value="">Select04</option>
<option value="">Select05</option>
</select>
<select className="select-form" name="" id="">
<option value="">Select01</option>
<option value="">Select02</option>
<option value="">Select03</option>
<option value="">Select04</option>
<option value="">Select05</option>
</select>
<select className="select-form" name="" id="" disabled>
<option value="">Select01</option>
<option value="">Select02</option>
<option value="">Select03</option>
<option value="">Select04</option>
<option value="">Select05</option>
</select>
</div>
</div>
</div>
<div className="design-box">
<h1 className="tit">TextArea</h1>
<div className="design-grid-wrap">
<textarea className="textarea-form" name="" id="" placeholder="TextArea Filed"></textarea>
<textarea className="textarea-form" name="" id="" disabled></textarea>
</div>
</div>
<style jsx>
{`
.form-design-wrap {
max-width: 1300px;
margin: 0px auto;
padding: 25px 20px;
}
.design-box {
padding: 15px;
box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
border-radius: 5px;
margin-bottom: 25px;
}
.tit {
font-size: 24px;
font-weight: 600;
color: #000;
border-bottom: 1px solid #000;
padding-bottom: 5px;
}
.design-grid-wrap {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-top: 20px;
gap: 10px;
}
.icon-bx {
width: 10px;
height: 10px;
margin-right: 5px;
background-color: #000;
}
`}
</style>
</div>
</>
)
}

View File

@ -0,0 +1,24 @@
import { ReactNode } from 'react'
interface SuitableLayoutProps {
children: ReactNode
}
export default function layout({ children }: SuitableLayoutProps) {
return (
<>
<div className="container">
<div className="sale-contents">
<div className="border-frame">
<div className="pw-guide">
<div className="pw-guide-txt">使.</div>
<div className="pw-guide-txt">11.</div>
<div className="pw-guide-txt">or屋根材名を直接入力してください.</div>
</div>
</div>
{children}
</div>
</div>
</>
)
}

View File

@ -1,7 +1,9 @@
import Suitable from '@/components/suitable/Suitable'
export default function page() {
return (
<>
<h1> </h1>
<Suitable />
</>
)
}

View File

@ -5,7 +5,6 @@ export default function page() {
return (
<>
<DataTable />
<DetailForm />
</>
)
}

View File

@ -1,5 +1,4 @@
import ListTable from '@/components/survey-sale/list/ListTable'
import SearchForm from '@/components/survey-sale/list/SearchForm'
export default function page() {
return (

View File

@ -3,10 +3,9 @@
interface LoadMoreButtonProps {
hasMore: boolean
onLoadMore: () => void
onScrollToTop: () => void
}
export default function LoadMoreButton({ hasMore, onLoadMore, onScrollToTop }: LoadMoreButtonProps) {
export default function LoadMoreButton({ hasMore, onLoadMore }: LoadMoreButtonProps) {
return (
<>
{hasMore ? (
@ -15,10 +14,7 @@ export default function LoadMoreButton({ hasMore, onLoadMore, onScrollToTop }: L
<i className="btn-edit"></i>
</button>
) : (
<button onClick={onScrollToTop} className="btn-frame n-blue icon">
<i className="btn-arr-up"></i>
</button>
<></>
)}
</>
)

View File

@ -1,9 +1,11 @@
'use client'
import { useReducer, useState } from 'react'
import type { SessionData } from '@/types/Auth'
import { useEffect, useReducer, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useQuery } from '@tanstack/react-query'
import { axiosInstance } from '@/libs/axios'
import { useSessionStore } from '@/store/session'
interface AccountState {
loginId: string
@ -21,6 +23,8 @@ export default function Login() {
//로그인 상태
const [isLogin, setIsLogin] = useState(false)
const { session, setSession } = useSessionStore()
const reducer = (state: AccountState, newState: Partial<AccountState>) => ({ ...state, ...newState })
const [account, setAccount] = useReducer(reducer, {
loginId: '',
@ -30,6 +34,7 @@ export default function Login() {
interface LoginData {
code: number
message: string | null
result: SessionData
}
const {
@ -43,7 +48,7 @@ export default function Login() {
loginId: account.loginId,
pwd: account.pwd,
})
router.push('/')
// router.push('/')
return data
},
@ -52,6 +57,17 @@ export default function Login() {
retry: false,
})
useEffect(() => {
setIsLogin(false)
if (loginData?.code === 200) {
setSession({
...session,
...loginData?.result,
})
router.push('/')
}
}, [loginData])
return (
<>
<div className="login-form-wrap">

View File

@ -23,11 +23,6 @@ export default function InquiryWriteForm() {
const file = Array.from(e.target.files || [])
setFormData({ ...formData, file: [...formData.file, ...file] })
}
const handleFileDelete = (fileToDelete: File) => {
setFormData({ ...formData, file: formData.file.filter((f) => f !== fileToDelete) })
}
const handleSubmit = () => {
console.log('submit: ', formData)
// router.push(`/inquiry`)
@ -60,7 +55,7 @@ export default function InquiryWriteForm() {
<div key={f.name}>
<div>{f.name}</div>
<div>
<button onClick={() => handleFileDelete(f)}>delete</button>
<button>delete</button>
</div>
</div>
))}

View File

@ -1,10 +1,13 @@
'use client'
import { useRouter } from 'next/navigation'
export default function ListForm() {
const router = useRouter()
return (
<>
<div className="sale-frame">
<div className="sale-form-bx">
<button className="btn-frame n-blue icon">
<button className="btn-frame n-blue icon" onClick={() => router.push('/inquiry/regist')}>
<i className="btn-arr"></i>
</button>
</div>

View File

@ -1,5 +1,52 @@
'use client'
import { use, useEffect, useState } from 'react'
import LoadMoreButton from '../LoadMoreButton'
const inquiryDummy = [
{ id: 1, category: '屋根', title: '屋根材適合性確認依頼', date: '2025.04.02', status: 'completed' },
{ id: 2, category: '外壁', title: '外壁仕上げ材確認', date: '2025.04.03', status: 'completed' },
{ id: 3, category: '設備', title: '換気システム図面確認', date: '2025.04.04', status: 'completed' },
{ id: 4, category: '基礎', title: '基礎配筋検査依頼', date: '2025.04.05', status: 'completed' },
{ id: 5, category: '内装', title: 'クロス仕様確認', date: '2025.04.06', status: 'waiting' },
{ id: 6, category: '構造', title: '耐震壁位置変更申請', date: '2025.04.07', status: 'completed' },
{ id: 7, category: '屋根', title: '雨樋取付方法確認', date: '2025.04.08', status: 'completed' },
{ id: 8, category: '外構', title: 'フェンス高さ変更相談', date: '2025.04.09', status: 'completed' },
{ id: 9, category: '設備', title: '給湯器設置位置確認', date: '2025.04.10', status: 'completed' },
{ id: 10, category: '外壁', title: 'タイル割付案確認依頼', date: '2025.04.11', status: 'waiting' },
{ id: 11, category: '内装', title: '照明配置図面確認', date: '2025.04.12', status: 'completed' },
{ id: 12, category: '構造', title: '梁補強案確認', date: '2025.04.13', status: 'completed' },
{ id: 13, category: '基礎', title: '杭長設計確認依頼', date: '2025.04.14', status: 'completed' },
{ id: 14, category: '屋根', title: '断熱材施工方法確認', date: '2025.04.15', status: 'completed' },
{ id: 15, category: '外構', title: '駐車場勾配図確認', date: '2025.04.16', status: 'completed' },
]
const badgeStyle = [
{
id: 'completed',
label: '回答完了',
color: 'blue',
},
{
id: 'waiting',
label: '回答待ち',
color: 'orange',
},
]
export default function ListTable() {
const [offset, setOffset] = useState(0)
const [hasMore, setHasMore] = useState(true)
const inquiryList = inquiryDummy.slice(0, offset + 10)
useEffect(() => {
if (inquiryDummy.length > offset + 10) {
setHasMore(true)
} else {
setHasMore(false)
}
}, [inquiryList])
return (
<>
<div className="sale-frame">
@ -13,10 +60,8 @@ export default function ListTable() {
<div className="filter-select">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
</select>
</div>
</div>
@ -25,43 +70,21 @@ export default function ListTable() {
<span>98</span>
</div>
<ul className="inquiry-list">
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge blue"></div>
</div>
</li>
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge orange"></div>
</div>
</li>
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge blue"></div>
</div>
</li>
<li className="inquiry-item">
<div className="inquiry-item-bx">
<div className="inquiry-item-category"></div>
<div className="inquiry-item-tit"></div>
<div className="inquiry-item-date">2025.04.02</div>
<div className="inquiry-badge badge orange"></div>
</div>
</li>
{inquiryList.map((inquiry) => (
<li className="inquiry-item" key={inquiry.id}>
<div className="inquiry-item-bx">
<div className="inquiry-item-category">{inquiry.category}</div>
<div className="inquiry-item-tit">{inquiry.title}</div>
<div className="inquiry-item-date">{inquiry.date}</div>
<div className={`inquiry-badge badge ${badgeStyle.find((badge) => badge.id === inquiry.status)?.color}`}>
{badgeStyle.find((badge) => badge.id === inquiry.status)?.label}
</div>
</div>
</li>
))}
</ul>
<div className="sale-edit-btn">
<button className="btn-frame n-blue icon">
<i className="btn-edit"></i>
</button>
<LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
</div>
</div>
</div>

View File

@ -0,0 +1,32 @@
export default function SuitableDetailPopup() {
return (
<div className="modal-popup">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<div className="modal-header-inner">
<div className="modal-name-wrap">
<div className="modal-img">
<img src="/assets/images/layout/modal_header_icon03.svg" alt="" />
</div>
<div className="modal-name"> </div>
</div>
<button className="modal-close"></button>
</div>
</div>
<div className="modal-body">
<div className={`compliance-check-bx act`}>
<div className="check-name-wrap">
<div className="check-name"></div>
<div className="check-name-btn">
<button className="bx-btn"></button>
</div>
</div>
<div className="compliance-check-pop-contents"></div>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -1,18 +1,51 @@
'use client'
import { useAddressStore } from '@/store/addressStore'
import { usePopupController } from '@/store/popupController'
import { useState } from 'react'
const dummyData = [
{
post_code: '123-567',
pref: '東京都',
city: '千代田区',
detail: '永田町ハイツ101号室',
},
{
post_code: '987-654',
pref: '大阪府',
city: '北区',
detail: '梅田スカイビル23階',
},
{
post_code: '456-789',
pref: '福岡県',
city: '博多区',
detail: '中洲マンション305号',
},
]
export default function ZipCodePopup() {
const [searchValue, setSearchValue] = useState('') //search 데이터 유무
const [selected, setSelected] = useState('')
const { setAddressData } = useAddressStore()
//search 데이터 value 추가
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value)
}
const popupController = usePopupController()
const handleApply = () => {
const addressData = dummyData.find((item) => item.post_code === selected)
setAddressData({
post_code: addressData?.post_code || '',
address: addressData?.pref || '',
address_detail: addressData?.city + ' ' + addressData?.detail || '',
})
popupController.setZipCodePopup(false)
}
return (
<>
<div className="modal-popup">
@ -50,31 +83,23 @@ export default function ZipCodePopup() {
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
{dummyData.map((item, index) => (
<tr key={`${item.post_code}-${index}`} onClick={() => setSelected(item.post_code)}>
<td>{item.pref}</td>
<td>{item.city}</td>
<td>{item.detail}</td>
</tr>
))}
</tbody>
</table>
<div className="btn-flex-wrap">
<div className="btn-bx">
<button className="btn-frame red icon">
<button className="btn-frame red icon" onClick={handleApply}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon">
<button className="btn-frame n-blue icon" onClick={() => popupController.setZipCodePopup(false)}>
<i className="btn-arr"></i>
</button>
</div>

View File

@ -0,0 +1,87 @@
'use client'
import { useState } from 'react'
import SuitableCheckData from './SuitableCheckData'
import SuitableNoData from './SuitableNoData'
export default function Suitable() {
const [reference, setReference] = useState(false)
return (
<div className="border-frame">
<div className="sale-form-bx">
<select className="select-form" name="" id="">
<option value="">.</option>
<option value="">.</option>
<option value="">.</option>
<option value="">.</option>
<option value="">.</option>
</select>
</div>
<div className="sale-form-bx">
<div className="search-input">
<input type="text" className="search-frame" placeholder="屋根材 製品名を入力してください." />
<button className="search-icon"></button>
</div>
</div>
<div className="compliance-check-wrap">
<div className={`compliance-check-bx ${reference ? 'act' : ''}`}>
<div className="check-name-wrap">
<div className="check-name"></div>
<div className="check-name-btn">
<button className="bx-btn" onClick={() => setReference(!reference)}></button>
</div>
</div>
<ul className="reference-list">
<li className="reference-item">
<div className="reference-item-bx">
<i className="compliance-icon check"></i>
</div>
</li>
<li className="reference-item">
<div className="reference-item-bx">
<i className="compliance-icon x"></i>
</div>
</li>
<li className="reference-item">
<div className="reference-item-bx">
<i className="compliance-icon quest"></i>
</div>
</li>
<li className="reference-item">
<div className="reference-item-bx">
<i className="compliance-icon tip"></i>
</div>
</li>
</ul>
</div>
{/* checkData */}
{/* 데이터 없을경우 버튼 영역 안보여야함 */}
<SuitableCheckData />
<SuitableCheckData />
<SuitableCheckData />
<SuitableCheckData />
</div>
{/* 데이터 없을경우 버튼 영역 안보여야함 */}
<div className="btn-flex-wrap com">
<div className="btn-bx">
<button className="btn-frame n-blue icon">
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon">
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon">
<i className="btn-arr"></i>
</button>
</div>
</div>
{/* 검색기록 없을떄 위에 두 영역 안보이고 이 부분만 보여야 함*/}
{/* <SuitableNoData /> */}
</div>
)
}

View File

@ -0,0 +1,68 @@
'use client'
export default function SuitableCheckData() {
return (
<>
<div className={`compliance-check-bx act`}>
<div className="check-name-wrap">
<div className="check-form-box com-tit">
<input type="checkbox" id="ch01" />
<label htmlFor="ch01"></label>
</div>
<div className="check-name-btn">
<button className="bx-btn"></button>
</div>
</div>
<ul className="reference-list check">
<li className="reference-item">
<div className="check-item-wrap">
<div className="check-form-box com-txt">
<input type="checkbox" id="ch02" />
<label htmlFor="ch02"> </label>
</div>
<div className="compliance-icon-wrap">
<i className="compliance-icon check"></i>
<i className="compliance-icon x"></i>
</div>
</div>
</li>
<li className="reference-item">
<div className="check-item-wrap">
<div className="check-form-box com-txt">
<input type="checkbox" id="ch03" />
<label htmlFor="ch03"> </label>
</div>
<div className="compliance-icon-wrap">
<i className="compliance-icon x"></i>
<i className="compliance-icon tip"></i>
</div>
</div>
</li>
<li className="reference-item">
<div className="check-item-wrap">
<div className="check-form-box com-txt">
<input type="checkbox" id="ch04" />
<label htmlFor="ch04">YGアンカー</label>
</div>
<div className="compliance-icon-wrap">
<i className="compliance-icon tip"></i>
<i className="compliance-icon quest"></i>
</div>
</div>
</li>
<li className="reference-item">
<div className="check-item-wrap">
<div className="check-form-box com-txt">
<input type="checkbox" id="ch05" />
<label htmlFor="ch05"></label>
</div>
<div className="compliance-icon-wrap">
<i className="compliance-icon check"></i>
</div>
</div>
</li>
</ul>
</div>
</>
)
}

View File

@ -0,0 +1,15 @@
export default function SuitableNoData() {
return (
<>
<div className="compliace-nosearch">
<span className="mb10"></span>
<span className="mb10"> </span>
<span>
<button className="btn-frame n-blue icon">
<i className="btn-arr"></i>
</button>
</span>
</div>
</>
)
}

View File

@ -3,7 +3,6 @@
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { usePathname, useRouter, useSearchParams, useParams } from 'next/navigation'
import { useEffect } from 'react'
import { usePopupController } from '@/store/popupController'
export default function NavTab() {
const router = useRouter()
@ -11,6 +10,7 @@ export default function NavTab() {
const searchParams = useSearchParams()
const id = searchParams.get('id')
const isTemp = searchParams.get('isTemp')
const params = useParams()
const detailId = params.id
@ -33,13 +33,17 @@ export default function NavTab() {
return
}
if (detailId) {
router.push(`/survey-sale/${detailId}?tab=basic-info`)
router.push(`/survey-sale/${detailId}`)
return
}
}
const handleRoofInfoClick = () => {
if (id) {
if (isTemp === 'true') {
alert('基本情報が一時保存された状態です。')
return
}
router.push(`/survey-sale/roof-info?id=${id}`)
return
}
@ -48,8 +52,7 @@ export default function NavTab() {
return
}
if (pathname === '/survey-sale/basic-info') {
// TODO: 팝업 추가
alert('Save essential information first')
alert('基本情報を先に保存してください。')
return null
}
}

View File

@ -1,22 +1,34 @@
'use client'
import { useServey } from '@/hooks/useSurvey'
import { useParams } from 'next/navigation'
import { useParams, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import { useState } from 'react'
import DetailForm from './DetailForm'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import RoofDetailForm from './RoofDetailForm'
export default function DataTable() {
const params = useParams()
const id = params.id
const searchParams = useSearchParams()
const tab = searchParams.get('tab')
const { surveyDetail, isLoadingSurveyDetail } = useServey(Number(id))
const [isTemporary, setIsTemporary] = useState(false)
const [isTemporary, setIsTemporary] = useState(true)
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
useEffect(() => {
if (!surveyDetail?.representative || !surveyDetail?.store || !surveyDetail?.construction_point) {
setIsTemporary(true)
if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) {
setIsTemporary(false)
}
}, [surveyDetail])
if (tab === 'roof-info') {
setRoofInfoSelected()
} else {
setBasicInfoSelected()
}
}, [surveyDetail, tab, setBasicInfoSelected, setRoofInfoSelected])
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
@ -52,9 +64,15 @@ export default function DataTable() {
<tr>
<th></th>
<td>
{surveyDetail?.submission_status && surveyDetail?.submission_date
? new Date(surveyDetail.submission_date).toLocaleString()
: '-'}
{surveyDetail?.submission_status && surveyDetail?.submission_date ? (
<>
{/* TODO: 제출한 판매점 ID 추가 필요 */}
<div>{new Date(surveyDetail.submission_date).toLocaleString()}</div>
<div> ID...</div>
</>
) : (
'-'
)}
</td>
</tr>
<tr>
@ -68,6 +86,11 @@ export default function DataTable() {
</tbody>
</table>
</div>
{tab === 'roof-info' ? (
<RoofDetailForm surveyDetail={surveyDetail} isLoadingSurveyDetail={isLoadingSurveyDetail} />
) : (
<DetailForm surveyDetail={surveyDetail} isLoadingSurveyDetail={isLoadingSurveyDetail} />
)}
</>
)
}

View File

@ -0,0 +1,65 @@
'use client'
import { useRouter } from 'next/navigation'
import { useServey } from '@/hooks/useSurvey'
export default function DetailButton({ isTemporary, surveyId }: { isTemporary: boolean; surveyId: number }) {
const router = useRouter()
const { submitSurvey, deleteSurvey } = useServey(surveyId)
const handleSubmit = async () => {
if (isTemporary) {
alert('一時保存されたデータは提出できません。')
return
}
if (confirm('提出しますか??')) {
if (surveyId) {
// TODO: 제출 페이지 추가
alert('SUBMIT POPUP!!!!!!!!!!!')
await submitSurvey()
}
}
}
const handleUpdate = () => {
router.push(`/survey-sale/basic-info?id=${surveyId}&isTemp=${isTemporary}`)
}
const handleDelete = async () => {
if (confirm('削除しますか?')) {
if (surveyId) {
await deleteSurvey()
router.push('/survey-sale')
}
}
}
return (
<div className="btn-flex-wrap">
{isTemporary ? (
<></>
) : (
<>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
</>
)}
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleUpdate}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleDelete}>
<i className="btn-arr"></i>
</button>
</div>
</div>
)
}

View File

@ -1,60 +1,27 @@
'use client'
import { useServey } from '@/hooks/useSurvey'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { useEffect, useState } from 'react'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
import DetailButton from './DetailButton'
import { SurveyBasicInfo } from '@/types/Survey'
export default function DetailForm() {
const router = useRouter()
const params = useParams()
const id = params.id
const searchParams = useSearchParams()
const tab = searchParams.get('tab')
const { surveyDetail, deleteSurvey, submitSurvey, isLoadingSurveyDetail } = useServey(Number(id))
const { setBasicInfoSelected, setRoofInfoSelected } = useSurveySaleTabState()
export default function DetailForm({
surveyDetail,
isLoadingSurveyDetail,
}: {
surveyDetail: SurveyBasicInfo | null
isLoadingSurveyDetail: boolean
}) {
const [isTemporary, setIsTemporary] = useState(true)
// TODO: 조사매물 지붕정보 퍼블 구현되면 탭 화면 변경 추가
useEffect(() => {
if (tab === 'basic-info') {
setBasicInfoSelected()
}
if (tab === 'roof-info') {
setRoofInfoSelected()
}
if (surveyDetail?.representative && surveyDetail?.store && surveyDetail?.construction_point) {
setIsTemporary(false)
}
}, [tab, setBasicInfoSelected, setRoofInfoSelected, surveyDetail])
}, [surveyDetail])
console.log('isTemporary', isTemporary)
console.log('surveyDetail', surveyDetail)
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
}
const handleSubmit = async () => {
if (confirm('submit?')) {
if (surveyDetail?.id) {
await submitSurvey()
}
}
}
const handleUpdate = () => {
router.push(`/survey-sale/basic-info?id=${id}`)
}
const handleDelete = async () => {
if (confirm('delete?')) {
if (surveyDetail?.id) {
await deleteSurvey()
router.push('/survey-sale')
}
}
}
return (
<>
<div className="sale-frame">
@ -89,34 +56,7 @@ export default function DetailForm() {
<input type="text" className="input-frame" disabled defaultValue={surveyDetail?.customer_name ?? ''} />
</div>
</div>
<div className="btn-flex-wrap">
{isTemporary ? (
<></>
) : (
<>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={() => router.push('/survey-sale')}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame red icon" onClick={handleSubmit}>
<i className="btn-arr"></i>
</button>
</div>
</>
)}
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleUpdate}>
<i className="btn-arr"></i>
</button>
</div>
<div className="btn-bx">
<button className="btn-frame n-blue icon" onClick={handleDelete}>
<i className="btn-arr"></i>
</button>
</div>
</div>
<DetailButton isTemporary={isTemporary} surveyId={Number(surveyDetail?.id)} />
</div>
</>
)

View File

@ -0,0 +1,256 @@
import { SurveyBasicInfo, SurveyDetailInfo } from '@/types/Survey'
import DetailButton from './DetailButton'
import { roof_material, supplementary_facilities } from './form/MultiCheckEtc'
import { selectBoxOptions } from './form/SelectBoxEtc'
import { radioEtcData } from './form/RadioEtc'
export default function RoofDetailForm({
surveyDetail,
isLoadingSurveyDetail,
}: {
surveyDetail: SurveyBasicInfo | null
isLoadingSurveyDetail: boolean
}) {
console.log(surveyDetail)
const makeNumArr = (value: string) => {
return value
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
}
if (isLoadingSurveyDetail) {
return <div>Loading...</div>
}
return (
<>
<div className="sale-frame">
<div className="data-form-wrap">
<div className="data-input-form-bx">
{/* 전기 계약 용량 */}
<div className="data-input-form-tit"></div>
<input className="input-frame" type="text" placeholder="-" readOnly value={surveyDetail?.detail_info?.contract_capacity ?? ''} />
</div>
{/* 전기 소매 회사 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<input className="input-frame" type="text" placeholder="-" readOnly value={surveyDetail?.detail_info?.retail_company ?? ''} />
</div>
{/* 전기 부대 설비 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-check-wrap">
{supplementary_facilities.map((item) => (
<div className="check-form-box" key={item.id}>
<input
type="checkbox"
id={`${item.id}`}
checked={makeNumArr(surveyDetail?.detail_info?.supplementary_facilities ?? '').includes(String(item.id))}
readOnly
/>
<label htmlFor={`${item.id}`}>{item.name}</label>
</div>
))}
<div className="check-form-box">
<input
type="checkbox"
id={`supplementary_facilities_etc`}
checked={surveyDetail?.detail_info?.supplementary_facilities_etc !== null}
readOnly
/>
<label htmlFor={`supplementary_facilities_etc`}> ()</label>
</div>
</div>
<div className="data-input">
<input
type="text"
className="input-frame"
placeholder="-"
readOnly
value={surveyDetail?.detail_info?.supplementary_facilities_etc ?? ''}
/>
</div>
</div>
{/* 설치 희망 시스템 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="installation_system" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 건축 연수 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="construction_year" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 지붕재 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-check-wrap">
{roof_material.map((item) => (
<div className="check-form-box" key={item.id}>
<input
type="checkbox"
id={`${item.id}`}
checked={makeNumArr(surveyDetail?.detail_info?.roof_material ?? '').includes(String(item.id))}
readOnly
/>
<label htmlFor={`${item.id}`}>{item.name}</label>
</div>
))}
<div className="check-form-box">
<input type="checkbox" id={`roof_material_etc`} checked={surveyDetail?.detail_info?.roof_material_etc !== null} readOnly />
<label htmlFor={`roof_material_etc`}> ()</label>
</div>
</div>
<div className="data-input">
<input type="text" className="input-frame" placeholder="-" readOnly value={surveyDetail?.detail_info?.roof_material_etc ?? ''} />
</div>
</div>
{/* 지붕 모양 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="roof_shape" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 지붕 경사도 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input className="input-frame" type="text" placeholder="-" readOnly value={surveyDetail?.detail_info?.roof_slope ?? ''} />
<span></span>
</div>
</div>
{/* 주택 구조 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="house_structure" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 서까래 재질 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="rafter_material" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 서까래 크기 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="rafter_size" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 서까래 피치 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="rafter_pitch" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 서까래 방향 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="rafter_direction" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 노지판 종류 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="open_field_plate_kind" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 노지판 두께 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input
className="input-frame"
type="text"
placeholder="-"
readOnly
value={surveyDetail?.detail_info?.open_field_plate_thickness ?? ''}
/>
<span>mm</span>
</div>
</div>
{/* 누수 흔적 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="leak_trace" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 방수재 종류 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="waterproof_material" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 단열재 유무 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<RadioSelected column="insulation_presence" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 구조 순서 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="structure_order" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 설치 가능 여부 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<SelectedBox column="installation_availability" detailInfoData={surveyDetail?.detail_info ?? null} />
</div>
{/* 메모 */}
<div className="data-input-form-bx">
<div className="data-input-form-tit"></div>
<div className="data-input">
<textarea className="textarea-form" placeholder="-" readOnly value={surveyDetail?.detail_info?.memo ?? ''} />
</div>
</div>
</div>
<DetailButton isTemporary={false} surveyId={Number(surveyDetail?.id)} />
</div>
</>
)
}
const SelectedBox = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo | null }) => {
const selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
const etcValue = detailInfoData?.[`${column}_etc` as keyof SurveyDetailInfo]
return (
<>
<select className="select-form mb10" name={column} id={column} disabled value={selectedId ? 'selected' : etcValue ? 'etc' : ''}>
<option value="">-</option>
<option value="etc"> ()</option>
<option value="selected">
{typeof selectedId === 'number' ? selectBoxOptions[column as keyof typeof selectBoxOptions][selectedId]?.name : ''}
</option>
</select>
{etcValue && <input type="text" className="input-frame" readOnly value={etcValue.toString()} />}
</>
)
}
const RadioSelected = ({ column, detailInfoData }: { column: string; detailInfoData: SurveyDetailInfo | null }) => {
let selectedId = detailInfoData?.[column as keyof SurveyDetailInfo]
if (column === 'leak_trace') {
selectedId = Number(selectedId)
}
let etcValue = null
if (column !== 'rafter_direction') {
etcValue = detailInfoData?.[`${column}_etc` as keyof SurveyDetailInfo]
}
const etcChecked = etcValue !== null && etcValue !== undefined && etcValue !== ''
return (
<>
{radioEtcData[column as keyof typeof radioEtcData].map((item) => (
<div className="radio-form-box mb10" key={item.id}>
<input type="radio" name={column} id={`${item.id}`} disabled checked={selectedId === item.id} />
<label htmlFor={`${item.id}`}>{item.label}</label>
</div>
))}
{column !== 'rafter_direction' && column !== 'leak_trace' && column !== 'insulation_presence' && (
<div className="radio-form-box mb10">
<input type="radio" name={column} id={`${column}_etc`} value="etc" disabled checked={etcChecked} />
<label htmlFor={`${column}_etc`}> ()</label>
</div>
)}
{etcChecked && (
<div className="data-input">
<input type="text" className="input-frame" placeholder="-" readOnly value={etcValue?.toString() ?? ''} />
</div>
)}
</>
)
}

View File

@ -5,12 +5,15 @@ import { SurveyBasicRequest } from '@/types/Survey'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState, useEffect } from 'react'
import { useSurveySaleTabState } from '@/store/surveySaleTabState'
import { usePopupController } from '@/store/popupController'
import { useAddressStore } from '@/store/addressStore'
import { useSessionStore } from '@/store/session'
const defaultBasicInfoForm: SurveyBasicRequest = {
representative: '',
store: null,
construction_point: null,
investigation_date: null,
investigation_date: new Date().toLocaleDateString('en-CA'),
building_name: null,
customer_name: null,
post_code: null,
@ -32,16 +35,25 @@ export default function BasicForm() {
const [basicInfoData, setBasicInfoData] = useState<SurveyBasicRequest>(defaultBasicInfoForm)
const { addressData } = useAddressStore()
const popupController = usePopupController()
useEffect(() => {
if (surveyDetail) {
const { id, updated_at, created_at, detail_info, ...rest } = surveyDetail
setBasicInfoData(rest)
}
}, [surveyDetail])
useEffect(() => {
if (addressData) {
setBasicInfoData({
...basicInfoData,
post_code: addressData.post_code,
address: addressData.address,
address_detail: addressData.address_detail,
})
}
setBasicInfoSelected()
}, [])
}, [surveyDetail, addressData])
const focusInput = (input: keyof SurveyBasicRequest) => {
const inputElement = document.getElementById(input)
@ -67,16 +79,17 @@ export default function BasicForm() {
const handleSave = async (isTemporary: boolean) => {
if (id) {
updateSurvey(basicInfoData)
alert('保存しました。')
router.push(`/survey-sale/${id}?tab=basic-info`)
}
if (isTemporary) {
const saveId = await createSurvey(basicInfoData)
alert('save success temporary id: ' + saveId)
alert('一時保存されました。')
router.push(`/survey-sale/${saveId}?tab=basic-info`)
} else {
if (validateSurvey(basicInfoData)) {
const saveId = await createSurvey(basicInfoData)
alert('save success id: ' + saveId)
alert('保存しました。 登録番号: ' + saveId)
router.push(`/survey-sale/${saveId}?tab=basic-info`)
}
}
@ -98,7 +111,6 @@ export default function BasicForm() {
id="representative"
value={basicInfoData.representative}
onChange={(e) => handleChange('representative', e.target.value)}
required
/>
</div>
<div className="data-input-form-bx">
@ -109,7 +121,6 @@ export default function BasicForm() {
id="store"
value={basicInfoData.store ?? ''}
onChange={(e) => handleChange('store', e.target.value)}
required
/>
</div>
<div className="data-input-form-bx">
@ -120,7 +131,6 @@ export default function BasicForm() {
id="construction_point"
value={basicInfoData.construction_point ?? ''}
onChange={(e) => handleChange('construction_point', e.target.value)}
required
/>
</div>
</div>
@ -138,7 +148,7 @@ export default function BasicForm() {
type="date"
className="date-frame"
id="investigation_date"
value={basicInfoData.investigation_date ?? new Date().toISOString().split('T')[0]}
value={basicInfoData.investigation_date ?? ''}
onChange={(e) => handleChange('investigation_date', e.target.value)}
/>
</div>
@ -192,7 +202,7 @@ export default function BasicForm() {
</div>
</div>
<div className="form-btn">
<button className="btn-frame n-blue icon">
<button className="btn-frame n-blue icon" onClick={() => popupController.setZipCodePopup(true)}>
便<i className="btn-arr"></i>
</button>
</div>

View File

@ -1,14 +1,14 @@
import { SurveyDetailRequest } from '@/types/Survey'
import { useState } from 'react'
import { useEffect, useState } from 'react'
const supplementary_facilities = [
export const supplementary_facilities = [
{ id: 1, name: 'エコキュート' }, //에코큐트
{ id: 2, name: 'エネパーム' }, //에네팜
{ id: 3, name: '蓄電池システム' }, //축전지시스템
{ id: 4, name: '太陽光発電' }, //태양광발전
]
const roof_material = [
export const roof_material = [
{ id: 1, name: 'スレート' }, //슬레이트
{ id: 2, name: 'アスファルトシングル' }, //아스팔트 싱글
{ id: 3, name: '瓦' }, //기와
@ -29,11 +29,22 @@ export default function MultiCheckbox({
const [isOtherChecked, setIsOtherChecked] = useState(false)
const [otherValue, setOtherValue] = useState('')
const handleCheckbox = (dataIndex: number) => {
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
const makeNumArr = (value: string) => {
return value
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
}
useEffect(() => {
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsOtherChecked(true)
setOtherValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleCheckbox = (dataIndex: number) => {
const value = makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? ''))
let newValue: string[]
if (value.includes(String(dataIndex))) {
@ -63,11 +74,7 @@ export default function MultiCheckbox({
const handleOtherCheckbox = () => {
if (column === 'roof_material') {
const value = String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.filter((v) => v.length > 0)
const value = makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? ''))
// 현재 선택된 항목 수
const currentSelected = value.length
@ -119,10 +126,7 @@ export default function MultiCheckbox({
<input
type="checkbox"
id={`${column}_ch${item.id}`}
checked={String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')
.split(',')
.map((v) => v.trim())
.includes(String(item.id))}
checked={makeNumArr(String(detailInfoData[column as keyof SurveyDetailRequest] ?? '')).includes(String(item.id))}
onChange={() => handleCheckbox(item.id)}
/>
<label htmlFor={`${column}_ch${item.id}`}>{item.name}</label>

View File

@ -1,17 +1,19 @@
'use client'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { SurveyDetailRequest } from '@/types/Survey'
type RadioEtcKeys = 'house_structure' | 'rafter_material' | 'waterproof_material' | 'insulation_presence'
type RadioEtcKeys = 'house_structure' | 'rafter_material' | 'waterproof_material' | 'insulation_presence' | 'rafter_direction' | 'leak_trace'
const translateJapanese: Record<RadioEtcKeys, string> = {
house_structure: '住宅構造',
rafter_material: '垂木材質',
waterproof_material: '防水材の種類',
insulation_presence: '断熱材の有無',
rafter_direction: '垂木の方向',
leak_trace: '水漏れの痕跡',
}
const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
export const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
house_structure: [
{
id: 1,
@ -44,6 +46,26 @@ const radioEtcData: Record<RadioEtcKeys, { id: number; label: string }[]> = {
label: 'あり',
},
],
rafter_direction: [
{
id: 1,
label: '垂直垂木',
},
{
id: 2,
label: '水平垂木',
},
],
leak_trace: [
{
id: 1,
label: 'あり',
},
{
id: 2,
label: 'なし',
},
],
}
export default function RadioEtc({
@ -58,6 +80,13 @@ export default function RadioEtc({
const [isEtcSelected, setIsEtcSelected] = useState(false)
const [etcValue, setEtcValue] = useState('')
useEffect(() => {
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsEtcSelected(true)
setEtcValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (column === 'insulation_presence') {
@ -110,7 +139,7 @@ export default function RadioEtc({
))}
{column !== 'insulation_presence' && (
<div className="radio-form-box mb10">
<input type="radio" name={column} id={`${column}`} value="etc" onChange={handleRadioChange} />
<input type="radio" name={column} id={`${column}_etc`} value="etc" onChange={handleRadioChange} checked={isEtcSelected} />
<label htmlFor={`${column}_etc`}> ()</label>
</div>
)}

View File

@ -65,15 +65,26 @@ export default function RoofInfoForm() {
useEffect(() => {
if (surveyDetail?.detail_info) {
const { id, updated_at, created_at, ...rest } = surveyDetail.detail_info
const { id, updated_at, created_at, basic_info_id, ...rest } = surveyDetail.detail_info
setDetailInfoData(rest)
}
}, [surveyDetail])
const handleNumberInput = (key: keyof SurveyDetailRequest, value: number | string) => {
if (typeof value === 'string') {
const numberValue = value === '' ? null : Number(value)
setDetailInfoData({ ...detailInfoData, [key]: numberValue })
if (key === 'roof_slope' || key === 'open_field_plate_thickness') {
const stringValue = value.toString()
if (stringValue.length > 5) {
alert('保存できるサイズを超えました。')
return
}
if (stringValue.includes('.')) {
const decimalPlaces = stringValue.split('.')[1].length
if (decimalPlaces > 1) {
alert('小数点以下1桁までしか許されません。')
return
}
}
setDetailInfoData({ ...detailInfoData, [key]: value.toString() })
} else {
setDetailInfoData({ ...detailInfoData, [key]: value })
}
@ -95,19 +106,30 @@ export default function RoofInfoForm() {
})
}
// TODO: 조사매물 저장 요구사항 정립 이후 수정 필요
const handleSave = async () => {
if (id) {
const emptyField = validateSurveyDetail(detailInfoData)
if (emptyField.trim() === '') {
createSurveyDetail({ surveyId: Number(id), surveyDetail: detailInfoData })
const updatedBasicInfoData = {
detail_info: detailInfoData,
}
try {
createSurveyDetail({
surveyId: Number(id),
surveyDetail: updatedBasicInfoData,
})
alert('調査物件を保存しました。')
} catch (error) {
alert(error)
throw new Error('failed to create survey detail: ' + error)
}
router.push(`/survey-sale`)
} else {
alert(emptyField + ' is required')
alert(emptyField + ' は必須項目です。')
focusOnInput(emptyField)
}
} else {
alert('save essential information first')
alert('基本情報を作成した後、屋根情報を作成することができます。')
}
}
const focusOnInput = (field: string) => {
@ -126,10 +148,11 @@ export default function RoofInfoForm() {
<div className="data-input-form-tit"></div>
<div className="data-input mb5">
<input
type="text"
type="number"
inputMode="decimal"
className="input-frame"
value={detailInfoData.contract_capacity?.split(' ')[0] ?? ''}
onChange={(e) => handleTextInput('contract_capacity', e.target.value)}
onChange={(e) => handleNumberInput('contract_capacity', e.target.value)}
/>
</div>
<div className="data-input">
@ -138,7 +161,11 @@ export default function RoofInfoForm() {
name="contract_capacity_unit"
id="contract_capacity_unit"
onChange={(e) => handleUnitInput(e.target.value)}
value={detailInfoData.contract_capacity?.split(' ')[1] ?? ''}
>
<option value="" hidden>
</option>
<option value="kVA">kVA</option>
<option value="A">A</option>
</select>
@ -179,10 +206,12 @@ export default function RoofInfoForm() {
<div className="data-input-form-tit"></div>
<div className="data-input flex">
<input
type="text"
type="number"
step={0.1}
inputMode="decimal"
className="input-frame"
value={detailInfoData.roof_slope ?? ''}
onChange={(e) => handleTextInput('roof_slope', e.target.value)}
onChange={(e) => handleNumberInput('roof_slope', e.target.value)}
/>
<span></span>
</div>
@ -205,7 +234,7 @@ export default function RoofInfoForm() {
name="rafter_direction"
id="rafter_direction_1"
value={1}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
onChange={(e) => handleNumberInput('rafter_direction', Number(e.target.value))}
checked={detailInfoData.rafter_direction === 1}
/>
<label htmlFor="rafter_direction_1"></label>
@ -216,7 +245,7 @@ export default function RoofInfoForm() {
name="rafter_direction"
id="rafter_direction_2"
value={2}
onChange={(e) => handleNumberInput('rafter_direction', e.target.value)}
onChange={(e) => handleNumberInput('rafter_direction', Number(e.target.value))}
checked={detailInfoData.rafter_direction === 2}
/>
<label htmlFor="rafter_direction_2"></label>
@ -232,10 +261,12 @@ export default function RoofInfoForm() {
</div>
<div className="data-input flex">
<input
type="text"
type="number"
step={0.1}
inputMode="decimal"
className="input-frame"
value={detailInfoData.open_field_plate_thickness ?? ''}
onChange={(e) => handleTextInput('open_field_plate_thickness', e.target.value)}
onChange={(e) => handleNumberInput('open_field_plate_thickness', e.target.value)}
/>
<span>mm</span>
</div>

View File

@ -1,7 +1,7 @@
import type { SurveyDetailRequest } from '@/types/Survey'
import { useEffect, useState } from 'react'
type SelectBoxKeys =
export type SelectBoxKeys =
| 'installation_system'
| 'construction_year'
| 'roof_shape'
@ -33,7 +33,7 @@ const translateJapanese: Record<SelectBoxKeys, string> = {
installation_availability: '屋根製品名 設置可否確認',
}
const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = {
export const selectBoxOptions: Record<SelectBoxKeys, { id: number; name: string }[]> = {
installation_system: [
{
id: 1,
@ -165,25 +165,28 @@ export default function SelectBoxForm({
const [etcValue, setEtcValue] = useState('')
useEffect(() => {
setEtcValue(detailInfoData[`${column}_etc`] ?? '')
}, [detailInfoData[`${column}_etc`]])
if (detailInfoData[`${column}_etc` as keyof SurveyDetailRequest]) {
setIsEtcSelected(true)
setEtcValue(detailInfoData[`${column}_etc` as keyof SurveyDetailRequest] as string)
}
}, [detailInfoData])
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value
if (column === 'installation_availability' || column === 'construction_year') {
setIsEtcSelected(value === '2') // 既築(2) 또는 未確認(2) 선택 시 input 활성화
setIsEtcSelected(value === '2') // 건축 연수 & 설치 가능 여부 컬럼 2번 선택 시 input 활성화
setDetailInfoData({
...detailInfoData,
[column]: Number(value),
})
} else if (value === 'etc') {
setIsEtcSelected(true)
setIsEtcSelected(true) // 기타 선택 시 input 활성화
setDetailInfoData({
...detailInfoData,
[column]: null,
})
} else {
setIsEtcSelected(false)
setIsEtcSelected(false) // 기타 선택 해제 시 input 비활성화
setEtcValue('')
setDetailInfoData({
...detailInfoData,
@ -207,9 +210,15 @@ export default function SelectBoxForm({
<div className="data-input-form-bx">
<div className={font[column]}>{translateJapanese[column]}</div>
<div className="data-input mb5">
<select className="select-form" name={column} id={column} onChange={handleSelectChange} value={detailInfoData[column] ?? ''}>
<select
className="select-form"
name={column}
id={column}
onChange={handleSelectChange}
value={detailInfoData[column] ? detailInfoData[column] : detailInfoData[`${column}_etc`] ? 'etc' : ''}
>
<option value="" hidden>
</option>
{selectBoxOptions[column].map((option) => (
<option key={option.id} value={option.id}>
@ -225,7 +234,13 @@ export default function SelectBoxForm({
className="input-frame"
value={etcValue ?? ''}
onChange={handleEtcInputChange}
disabled={column === 'installation_availability' || column === 'construction_year' ? !Boolean(detailInfoData[column]) : !isEtcSelected}
disabled={
column === 'installation_availability'
? !Boolean(detailInfoData[column])
: column === 'construction_year'
? detailInfoData[column] !== 2 // 既築(2)가 아닐 때 비활성화
: !isEtcSelected
}
/>
</div>
</div>

View File

@ -2,91 +2,79 @@
import LoadMoreButton from '@/components/LoadMoreButton'
import { useServey } from '@/hooks/useSurvey'
import { useEffect, useMemo, useState } from 'react'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import SearchForm from './SearchForm'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
import { useSessionStore } from '@/store/session'
export default function ListTable() {
const router = useRouter()
const { surveyList, isLoadingSurveyList } = useServey()
const { surveyList, isLoadingSurveyList, surveyListCount } = useServey()
const { offset, setOffset } = useSurveyFilterStore()
const [visibleItems, setVisibleItems] = useState(5)
const [search, setSearch] = useState('')
const [isMyPostsOnly, setIsMyPostsOnly] = useState(false)
const [heldSurveyList, setHeldSurveyList] = useState<typeof surveyList>([])
const [hasMore, setHasMore] = useState(false)
// TODO: 로그인 구현 이후 USERNAME 변경
const username = 'test'
const filteredSurveyList = useMemo(() => {
let filtered = surveyList
if (search.trim().length > 0) {
filtered = filtered.filter((survey) => survey.building_name?.includes(search))
}
if (isMyPostsOnly) {
filtered = filtered.filter((survey) => survey.representative === username)
}
return filtered
}, [surveyList, search, isMyPostsOnly, username])
const { session } = useSessionStore()
console.log('session:: ', session)
useEffect(() => {
setHasMore(filteredSurveyList.length > visibleItems)
}, [filteredSurveyList, visibleItems])
const handleLoadMore = () => {
const newVisibleItems = Math.min(visibleItems + 5, filteredSurveyList.length)
setVisibleItems(newVisibleItems)
setHasMore(newVisibleItems < filteredSurveyList.length)
}
const handleScrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
if (surveyList && surveyList.length > 0) {
if (offset === 0) {
setHeldSurveyList(surveyList)
} else {
const remainingList = heldSurveyList.slice(offset, offset + 10)
if (JSON.stringify(remainingList) !== JSON.stringify(surveyList)) {
setHeldSurveyList((prev) => [...prev, ...surveyList])
}
}
setHasMore(surveyListCount > offset + 10)
}
}, [surveyList, surveyListCount, offset])
const handleDetailClick = (id: number) => {
router.push(`/survey-sale/${id}`)
}
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value)
setVisibleItems(5)
const handleItemsInit = () => {
setHeldSurveyList([])
setOffset(0)
}
const handleMyPostsToggle = () => {
setIsMyPostsOnly((prev) => !prev)
setVisibleItems(5)
}
if (isLoadingSurveyList) {
return <div>Loading...</div>
}
// TODO: 로딩 처리 필요
return (
<>
<SearchForm handleSearch={handleSearchChange} handleMyPosts={handleMyPostsToggle} />
<div className="sale-frame">
<ul className="sale-list-wrap">
{filteredSurveyList.slice(0, visibleItems).map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx">
<div className="sale-item-date-bx">
<div className="sale-item-num">{survey.id}</div>
<div className="sale-item-date">{survey.investigation_date}</div>
<SearchForm onItemsInit={handleItemsInit} />
{heldSurveyList.length > 0 ? (
<div className="sale-frame">
<ul className="sale-list-wrap">
{heldSurveyList.map((survey) => (
<li className="sale-list-item cursor-pointer" key={survey.id} onClick={() => handleDetailClick(survey.id)}>
<div className="sale-item-bx">
<div className="sale-item-date-bx">
<div className="sale-item-num">{survey.id}</div>
<div className="sale-item-date">{survey.investigation_date}</div>
</div>
<div className="sale-item-tit">{survey.building_name}</div>
<div className="sale-item-customer">{survey.customer_name}</div>
<div className="sale-item-update-bx">
<div className="sale-item-name">{survey.representative}</div>
<div className="sale-item-update">{new Date(survey.updated_at).toLocaleString()}</div>
</div>
</div>
<div className="sale-item-tit">{survey.building_name}</div>
<div className="sale-item-customer">{survey.customer_name}</div>
<div className="sale-item-update-bx">
<div className="sale-item-name">{survey.representative}</div>
<div className="sale-item-update">{new Date(survey.updated_at).toLocaleString()}</div>
</div>
</div>
</li>
))}
</ul>
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={handleLoadMore} onScrollToTop={handleScrollToTop} />
</li>
))}
</ul>
<div className="sale-edit-btn">
<LoadMoreButton hasMore={hasMore} onLoadMore={() => setOffset(offset + 10)} />
</div>
</div>
</div>
) : (
<div>
<p></p>
</div>
)}
</>
)
}

View File

@ -1,9 +1,25 @@
'use client'
import { SEARCH_OPTIONS, SEARCH_OPTIONS_ENUM, useSurveyFilterStore } from '@/store/surveyFilterStore'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
export default function SearchForm({ handleSearch, handleMyPosts }: { handleSearch: (e: React.ChangeEvent<HTMLInputElement>) => void, handleMyPosts: () => void }) {
export default function SearchForm({ onItemsInit }: { onItemsInit: () => void }) {
const router = useRouter()
const { setSearchOption, setSort, setIsMySurvey, setKeyword, isMySurvey, keyword, searchOption, sort } = useSurveyFilterStore()
const [searchKeyword, setSearchKeyword] = useState(keyword)
const username = 'test'
const handleSearch = () => {
if (searchKeyword.trim().length < 2) {
alert('2文字以上入力してください')
return
}
setKeyword(searchKeyword)
onItemsInit()
}
return (
<div className="sale-frame">
<div className="sale-form-bx">
@ -12,33 +28,64 @@ export default function SearchForm({ handleSearch, handleMyPosts }: { handleSear
</button>
</div>
<div className="sale-form-bx">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<select
className="select-form"
name="search-option"
id="search-option"
value={searchOption}
onChange={(e) => setSearchOption(e.target.value as SEARCH_OPTIONS_ENUM)}
>
{SEARCH_OPTIONS.map((option) => (
<option key={option.id} value={option.id}>
{option.label}
</option>
))}
</select>
</div>
<div className="sale-form-bx">
<div className="search-input">
<input type="text" className="search-frame" placeholder="タイトルを入力してください. (2文字以上)" onChange={handleSearch} />
<button className="search-icon"></button>
<input
type="text"
className="search-frame"
value={searchKeyword}
placeholder="タイトルを入力してください. (2文字以上)"
onChange={(e) => setSearchKeyword(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch()
}
}}
/>
<button className="search-icon" onClick={handleSearch}></button>
</div>
</div>
<div className="sale-form-bx">
<div className="check-form-box">
<input type="checkbox" id="ch01" onClick={handleMyPosts} />
<input
type="checkbox"
id="ch01"
checked={isMySurvey === username}
onChange={() => {
setIsMySurvey(isMySurvey === username ? null : username)
onItemsInit()
}}
/>
<label htmlFor="ch01"></label>
</div>
</div>
<div className="sale-form-bx">
<select className="select-form" name="" id="">
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<option value=""></option>
<select
className="select-form"
name="sort-option"
id="sort-option"
value={sort}
onChange={(e) => {
setSort(e.target.value as 'created' | 'updated')
onItemsInit()
}}
>
<option value="created"></option>
<option value="updated"></option>
</select>
</div>
</div>

View File

@ -1,27 +1,8 @@
'use client'
import { useHeaderStore } from '@/store/header'
import { useSideNavState } from '@/store/sideNavState'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export default function Main() {
const router = useRouter()
const pathname = usePathname()
const { setBackBtn } = useHeaderStore()
const { reset } = useSideNavState()
/**
*
*
*/
useEffect(() => {
if (pathname === '/') {
setBackBtn(false)
}
//사이드바 초기화
reset()
}, [pathname])
return (
<>
@ -30,7 +11,7 @@ export default function Main() {
<div className="head-block-tit"></div>
<div className="head-block-text">使</div>
<div className="head-block-link-wrap">
<button className="head-block-link">
<button className="head-block-link" onClick={() => router.push('/suitable')}>
<i className="block-arr"></i>
</button>
</div>

View File

@ -1,8 +1,12 @@
'use client'
import { usePopupController } from '@/store/popupController'
import MemberInfomationPopup from '../popup/MemberInformationPopup'
import ZipCodePopup from '../popup/ZipCodePopup'
import Alert from './common/Alert'
import DoubleBtnAlert from './common/DoubleBtnAlert'
import SuitableDetailPopup from '../popup/SuitableDetailPopup'
export default function PopupController() {
const popupController = usePopupController()
@ -11,6 +15,9 @@ export default function PopupController() {
<>
{popupController.memberInfomationPopup && <MemberInfomationPopup />}
{popupController.zipCodePopup && <ZipCodePopup />}
{popupController.alert && <Alert />}
{popupController.alert2 && <DoubleBtnAlert />}
{popupController.suitableDetailPopup && <SuitableDetailPopup />}
</>
)
}

View File

@ -0,0 +1,24 @@
'use client'
import { usePopupController } from '@/store/popupController'
export default function Alert() {
const { alertMsg, alertBtn } = usePopupController()
return (
<div className="modal-popup alert">
<div className="modal-dialog">
<div className="modal-content">
<div className="alert-tit">{alertMsg}</div>
<div className="alert-btn-wrap">
<div className="alert-btn-bx">
<button className="btn-frame red min" onClick={() => alertBtn()}>
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,30 @@
'use client'
import { usePopupController } from '@/store/popupController'
import React from 'react'
export default function DoubleBtnAlert() {
const { alertMsg, alert2BtnYes, alert2BtnNo } = usePopupController()
return (
<div className="modal-popup alert">
<div className="modal-dialog">
<div className="modal-content">
<div className="alert-tit">{alertMsg}</div>
<div className="alert-btn-wrap">
<div className="alert-btn-bx">
<button className="btn-frame red min" onClick={() => alert2BtnYes()}>
</button>
</div>
<div className="alert-btn-bx">
<button className="btn-frame n-blue min" onClick={() => alert2BtnNo()}>
</button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -12,6 +12,8 @@ import type { HeaderProps } from '@/types/Header'
import 'swiper/css'
import { axiosInstance } from '@/libs/axios'
import { useSessionStore } from '@/store/session'
import { useQueryClient } from '@tanstack/react-query'
// type HeaderProps = {
// name: string //header 이름
@ -24,13 +26,18 @@ export default function Header({ name }: HeaderProps) {
const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState()
const { backBtn } = useHeaderStore()
const { session, reset } = useSessionStore()
const queryClient = useQueryClient()
if (pathname === '/login') {
return null
}
const handleLogout = async () => {
reset()
const { data } = await axiosInstance(null).get('/api/auth/logout')
if (data.code === 200) {
queryClient.clear()
router.push('/login')
}
}
@ -60,8 +67,8 @@ export default function Header({ name }: HeaderProps) {
<img src="/assets/images/layout/side_nav_profile.svg" alt="profile" />
</div>
<div className="profile-group">
<div className="profile-name">HONG GILDONG</div>
<div className="profile-company">Interplug corp.</div>
<div className="profile-name">{session.userNm}</div>
<div className="profile-company">{session.category}</div>
</div>
</div>
<div className="side-close-wrap">

View File

@ -1,28 +1,33 @@
import { axiosInstance } from '@/libs/axios'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { SurveyBasicInfo, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
import type { SurveyBasicInfo, SurveyBasicRequest, SurveyDetailInfo, SurveyDetailRequest, SurveyDetailCoverRequest } from '@/types/Survey'
import { axiosInstance } from '@/libs/axios'
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
export function useServey(id?: number): {
surveyList: SurveyBasicInfo[] | []
surveyDetail: SurveyBasicInfo | null
surveyListCount: number
isLoadingSurveyList: boolean
isLoadingSurveyDetail: boolean
isCreatingSurvey: boolean
isUpdatingSurvey: boolean
isDeletingSurvey: boolean
createSurvey: (survey: SurveyBasicRequest) => Promise<number>
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailRequest }) => void
createSurveyDetail: (params: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => void
updateSurvey: (survey: SurveyBasicRequest) => void
deleteSurvey: () => Promise<boolean>
submitSurvey: () => void
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
} {
const queryClient = useQueryClient()
const { keyword, searchOption, isMySurvey, sort, offset } = useSurveyFilterStore()
const { data: surveyList, isLoading: isLoadingSurveyList } = useQuery({
queryKey: ['survey', 'list'],
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset],
queryFn: async () => {
const resp = await axiosInstance.get<SurveyBasicInfo[]>('/api/survey-sales')
const resp = await axiosInstance(null).get<SurveyBasicInfo[]>('/api/survey-sales', {
params: { keyword, searchOption, isMySurvey, sort, offset },
})
return resp.data
},
})
@ -32,15 +37,25 @@ export function useServey(id?: number): {
queryFn: async () => {
if (id === undefined) throw new Error('id is required')
if (id === null) return null
const resp = await axiosInstance.get<SurveyBasicInfo>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).get<SurveyBasicInfo>(`/api/survey-sales/${id}`)
return resp.data
},
enabled: id !== undefined,
})
const { data: surveyListCount } = useQuery({
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort],
queryFn: async () => {
const resp = await axiosInstance(null).get<number>('/api/survey-sales', {
params: { keyword, searchOption, isMySurvey, sort },
})
return resp.data
},
})
const { mutateAsync: createSurvey, isPending: isCreatingSurvey } = useMutation({
mutationFn: async (survey: SurveyBasicRequest) => {
const resp = await axiosInstance.post<SurveyBasicInfo>('/api/survey-sales', survey)
const resp = await axiosInstance(null).post<SurveyBasicInfo>('/api/survey-sales', survey)
return resp.data.id ?? 0
},
onSuccess: (data) => {
@ -54,7 +69,7 @@ export function useServey(id?: number): {
mutationFn: async (survey: SurveyBasicRequest) => {
console.log('updateSurvey:: ', survey)
if (id === undefined) throw new Error('id is required')
const resp = await axiosInstance.put<SurveyBasicInfo>(`/api/survey-sales/${id}`, survey)
const resp = await axiosInstance(null).put<SurveyBasicInfo>(`/api/survey-sales/${id}`, survey)
return resp.data
},
onSuccess: () => {
@ -66,7 +81,7 @@ export function useServey(id?: number): {
const { mutateAsync: deleteSurvey, isPending: isDeletingSurvey } = useMutation({
mutationFn: async () => {
if (id === null) throw new Error('id is required')
const resp = await axiosInstance.delete<boolean>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).delete<boolean>(`/api/survey-sales/${id}`)
return resp.data
},
onSuccess: () => {
@ -75,9 +90,9 @@ export function useServey(id?: number): {
},
})
const { mutateAsync: createSurveyDetail, isPending: isCreatingSurveyDetail } = useMutation({
mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailRequest }) => {
const resp = await axiosInstance.post<SurveyDetailInfo>(`/api/survey-sales/${surveyId}`, surveyDetail)
const { mutateAsync: createSurveyDetail } = useMutation({
mutationFn: async ({ surveyId, surveyDetail }: { surveyId: number; surveyDetail: SurveyDetailCoverRequest }) => {
const resp = await axiosInstance(null).patch<SurveyDetailInfo>(`/api/survey-sales/${surveyId}`, surveyDetail)
return resp.data
},
onSuccess: () => {
@ -89,7 +104,9 @@ export function useServey(id?: number): {
const { mutateAsync: submitSurvey } = useMutation({
mutationFn: async () => {
if (id === undefined) throw new Error('id is required')
const resp = await axiosInstance.patch<boolean>(`/api/survey-sales/${id}`)
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
submit: true,
})
return resp.data
},
onSuccess: () => {
@ -108,31 +125,29 @@ export function useServey(id?: number): {
'waterproof_material',
'insulation_presence',
'structure_order',
] as const;
] as const
const etcFields = [
'installation_system',
'construction_year',
'rafter_size',
'rafter_pitch',
'waterproof_material',
'structure_order',
] as const;
const etcFields = ['installation_system', 'construction_year', 'rafter_size', 'rafter_pitch', 'waterproof_material', 'structure_order'] as const
const emptyField = requiredFields.find((field) => {
if (etcFields.includes(field as (typeof etcFields)[number])) {
return surveyDetail[field as keyof SurveyDetailRequest] === null &&
surveyDetail[`${field}_etc` as keyof SurveyDetailRequest] === null;
return surveyDetail[field as keyof SurveyDetailRequest] === null && surveyDetail[`${field}_etc` as keyof SurveyDetailRequest] === null
}
return surveyDetail[field as keyof SurveyDetailRequest] === null;
});
return surveyDetail[field as keyof SurveyDetailRequest] === null
})
return emptyField || '';
};
const contractCapacity = surveyDetail.contract_capacity
if (contractCapacity && contractCapacity.trim() !== '' && contractCapacity.split(' ')?.length === 1) {
return 'contract_capacity_unit'
}
return emptyField || ''
}
return {
surveyList: surveyList || [],
surveyDetail: surveyDetail || null,
surveyListCount: surveyListCount || 0,
isLoadingSurveyList,
isLoadingSurveyDetail,
isCreatingSurvey,

View File

@ -0,0 +1,71 @@
'use client'
import { useAlertSwitch } from '@/store/alertSwitch'
import { useHeaderStore } from '@/store/header'
import { usePopupController } from '@/store/popupController'
import { useSideNavState } from '@/store/sideNavState'
import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
declare global {
interface Window {
alert2: (msg: string, alert2BtnYes?: () => void, alert2BtnNo?: () => void) => void
}
}
export default function EdgeProvider({ children }: React.PropsWithChildren) {
const pathname = usePathname()
const { setBackBtn } = useHeaderStore()
const { reset } = useSideNavState()
const { setAlertMsg, setAlertBtn, setAlert, setAlert2, setAlert2BtnYes, setAlert2BtnNo } = usePopupController()
const { alertKind } = useAlertSwitch()
const alertFunc = (msg: string, alertBtn: Function) => {
console.log('🚀 ~ alertFunc ~ msg:', msg)
setAlertMsg(msg)
setAlertBtn(alertBtn)
setAlert(true)
}
/**
* alert2
* @param msg alert
* @param alertBtn2Yes alert
* @param alertBtn2No alert
*/
const alertFunc2 = (msg: string, alertBtn2Yes: Function, alertBtn2No: Function) => {
console.log('🚀 ~ alertFunc ~ msg:', msg)
setAlertMsg(msg)
setAlert2BtnYes(alertBtn2Yes)
setAlert2BtnNo(alertBtn2No)
setAlert2(true)
}
//alert 함수 변경해서 바인딩
useEffect(() => {
if (alertKind === 'single') {
window.alert = function (msg, alertBtn = () => setAlert(false)) {
alertFunc(msg, alertBtn)
}
} else if (alertKind === 'multi') {
window.alert = function (msg, alert2BtnYes = () => setAlert2(false), alert2BtnNo = () => setAlert2(false)) {
alertFunc2(msg, alert2BtnYes, alert2BtnNo)
}
}
}, [alertKind])
/**
*
*
*/
useEffect(() => {
if (pathname === '/') {
setBackBtn(false)
} else {
setBackBtn(true)
}
//사이드바 초기화
reset()
}, [pathname])
return <>{children}</>
}

19
src/store/addressStore.ts Normal file
View File

@ -0,0 +1,19 @@
import { create } from 'zustand'
type AddressData = {
post_code: string
address: string
address_detail: string
}
interface AddressState {
addressData: AddressData | null
setAddressData: (address: AddressData) => void
resetAddressData: () => void
}
export const useAddressStore = create<AddressState>((set) => ({
addressData: null,
setAddressData: (address) => set({ addressData: address }),
resetAddressData: () => set({ addressData: null }),
}))

21
src/store/alertSwitch.ts Normal file
View File

@ -0,0 +1,21 @@
import { create } from 'zustand'
type AlertSwitchState = {
alertKind: string
setAlertKind: (value: string) => void
reset: () => void
}
type InitialState = {
alertKind: string
}
const initialState: InitialState = {
alertKind: 'single',
}
export const useAlertSwitch = create<AlertSwitchState>((set) => ({
...initialState,
setAlertKind: (value: string) => set((state) => ({ ...state, alertKind: value })),
reset: () => set(initialState),
}))

View File

@ -0,0 +1,41 @@
import { create } from 'zustand'
export const FILTER_OPTIONS = [
{
id: 'all',
label: '全体',
},
{
id: 'completed',
label: '回答完了',
},
{
id: 'waiting',
label: '回答待ち',
},
]
export type FILTER_OPTIONS_ENUM = (typeof FILTER_OPTIONS)[number]['id']
type InquiryFilterState = {
keyword: string
filter: FILTER_OPTIONS_ENUM
isMySurvey: string | null
offset: number
setKeyword: (keyword: string) => void
setFilter: (filter: FILTER_OPTIONS_ENUM) => void
setIsMySurvey: (isMySurvey: string | null) => void
setOffset: (offset: number) => void
reset: () => void
}
export const useInquiryFilterStore = create<InquiryFilterState>((set) => ({
keyword: '',
filter: 'all',
isMySurvey: null,
offset: 0,
setKeyword: (keyword) => set({ keyword }),
setFilter: (filter) => set({ filter }),
setIsMySurvey: (isMySurvey) => set({ isMySurvey }),
setOffset: (offset) => set({ offset }),
reset: () => set({ keyword: '', filter: 'all', isMySurvey: null, offset: 0 }),
}))

View File

@ -3,23 +3,59 @@ import { create } from 'zustand'
type PoupControllerState = {
memberInfomationPopup: boolean
zipCodePopup: boolean
alertMsg: string
setAlertMsg: (value: string) => void
alert: boolean
alertBtn: Function
alert2: boolean
alert2BtnYes: Function
alert2BtnNo: Function
setMemberInfomationPopup: (value: boolean) => void
setZipCodePopup: (value: boolean) => void
setAlert: (value: boolean) => void
setAlertBtn: (value: Function) => void
setAlert2: (value: boolean) => void
setAlert2BtnYes: (value: Function) => void
setAlert2BtnNo: (value: Function) => void
suitableDetailPopup: boolean
setSuitableDetailPopup: (value: boolean) => void
reset: () => void
}
type InitialState = {
memberInfomationPopup: boolean
zipCodePopup: boolean
alertMsg: string
alert: boolean
alertBtn: Function
alert2: boolean
alert2BtnYes: Function
alert2BtnNo: Function
suitableDetailPopup: boolean
}
const initialState: InitialState = {
memberInfomationPopup: false,
zipCodePopup: false,
alertMsg: '',
alert: false,
alertBtn: () => {},
alert2: false,
alert2BtnYes: () => {},
alert2BtnNo: () => {},
suitableDetailPopup: false,
}
export const usePopupController = create<PoupControllerState>((set) => ({
...initialState,
setMemberInfomationPopup: (value: boolean) => set((state) => ({ ...state, memberInfomationPopup: value })),
setZipCodePopup: (value: boolean) => set((state) => ({ ...state, zipCodePopup: value })),
setAlertMsg: (value: string) => set((state) => ({ ...state, alertMsg: value })),
setAlert: (value: boolean) => set((state) => ({ ...state, alert: value })),
setAlertBtn: (value: Function) => set((state) => ({ ...state, alertBtn: value })),
setAlert2: (value: boolean) => set((state) => ({ ...state, alert2: value })),
setAlert2BtnYes: (value: Function) => set((state) => ({ ...state, alert2BtnYes: value })),
setAlert2BtnNo: (value: Function) => set((state) => ({ ...state, alert2BtnNo: value })),
setSuitableDetailPopup: (value: boolean) => set((state) => ({ ...state, suitableDetailPopup: value })),
reset: () => set(initialState),
}))

51
src/store/session.ts Normal file
View File

@ -0,0 +1,51 @@
import type { SessionData } from '@/types/Auth'
import { create } from 'zustand'
type SessionState = {
session: SessionData
setSession: (session: SessionData) => void
reset: () => void
}
type InitialState = {
session: SessionData
}
const initialState: InitialState = {
session: {
langCd: null,
currPage: 0,
rowCount: 0,
startRow: null,
endRow: null,
compCd: null,
agencyStoreId: null,
storeId: null,
userId: null,
category: null,
userNm: null,
userNmKana: null,
telNo: null,
fax: null,
email: null,
lastEditUser: null,
storeGubun: null,
pwCurr: null,
pwdInitYn: null,
apprStatCd: null,
loginFailCnt: 0,
loginFailMinYn: null,
priceViewStatCd: null,
groupId: null,
storeLvl: null,
custCd: null,
builderNo: null,
isLoggedIn: false,
},
}
export const useSessionStore = create<SessionState>((set) => ({
...initialState,
setSession: (value: SessionData) => set((state) => ({ ...state, session: { ...state.session, ...value } })),
reset: () => set(initialState),
}))

View File

@ -1,27 +1,94 @@
import { create } from 'zustand'
// export type SEARCH_OPTIONS_ENUM = 'all' | 'id' | 'building_name' | 'representative' | 'store' | 'construction_point'
// | 'store_id' | 'construction_id'
// export type SEARCH_OPTIONS_PARTNERS_ENUM = 'all' | 'id' | 'building_name' | 'representative'
// export type SORT_OPTIONS_ENUM = 'created' | 'updated'
export const SEARCH_OPTIONS = [
{
id: 'all',
label: '全体',
},
{
id: 'id',
label: '登録番号',
},
{
id: 'building_name',
label: '建物名',
},
{
id: 'representative',
label: '作成者',
},
{
id: 'store',
label: '販売店名',
},
// {
// id: 'store_id',
// label: '販売店ID',
// },
{
id: 'construction_point',
label: '施工店名',
},
// {
// id: 'construction_id',
// label: '施工店ID',
// },
]
export const SEARCH_OPTIONS_PARTNERS = [
{
id: 'all',
label: '全体',
},
{
id: 'id',
label: '登録番号',
},
{
id: 'building_name',
label: '建物名',
},
{
id: 'representative',
label: '作成者',
},
]
export type MEMBER_TYPE = 'hwj' | 'order' | 'musubi' | 'partner'
export type SEARCH_OPTIONS_ENUM = (typeof SEARCH_OPTIONS)[number]['id']
export type SEARCH_OPTIONS_PARTNERS_ENUM = (typeof SEARCH_OPTIONS_PARTNERS)[number]['id']
export type SORT_OPTIONS_ENUM = 'created' | 'updated'
type SurveyFilterState = {
keyword: string;
searchOption: string;
isMySurvey: boolean;
sort: 'recent' | 'updated';
setKeyword: (keyword: string) => void;
setSearchOption: (searchOption: string) => void;
setIsMySurvey: (isMySurvey: boolean) => void;
setSort: (sort: 'recent' | 'updated') => void;
reset: () => void;
keyword: string
searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM
isMySurvey: string | null
sort: SORT_OPTIONS_ENUM
offset: number
setKeyword: (keyword: string) => void
setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => void
setIsMySurvey: (isMySurvey: string | null) => void
setSort: (sort: SORT_OPTIONS_ENUM) => void
setOffset: (offset: number) => void
reset: () => void
}
export const useSurveyFilterStore = create<SurveyFilterState>((set) => ({
keyword: '',
searchOption: '',
isMySurvey: false,
sort: 'recent',
setKeyword: (keyword: string) => set({ keyword }),
setSearchOption: (searchOption: string) => set({ searchOption }),
setIsMySurvey: (isMySurvey: boolean) => set({ isMySurvey }),
setSort: (sort: 'recent' | 'updated') => set({ sort }),
reset: () => set({ keyword: '', searchOption: '', isMySurvey: false, sort: 'recent' }),
keyword: '',
searchOption: 'all',
isMySurvey: null,
sort: 'created',
offset: 0,
setKeyword: (keyword: string) => set({ keyword }),
setSearchOption: (searchOption: SEARCH_OPTIONS_ENUM | SEARCH_OPTIONS_PARTNERS_ENUM) => set({ searchOption }),
setIsMySurvey: (isMySurvey: string | null) => set({ isMySurvey }),
setSort: (sort: SORT_OPTIONS_ENUM) => set({ sort }),
setOffset: (offset: number) => set({ offset }),
reset: () => set({ keyword: '', searchOption: 'all', isMySurvey: null, sort: 'created', offset: 0 }),
}))

View File

@ -82,13 +82,13 @@ input::-webkit-inner-spin-button {
width: 100%;
padding: 0 10px;
height: 40px;
font-size: $font-s-13;
color: $font-c;
font-weight: $font-w-400;
background-color: $white-fff;
border: 1px solid #D5DEE8;
border-radius: 4px;
input{
font-size: $font-s-13;
color: $font-c;
font-weight: $font-w-400;
width: 100%;
height: 100%;
background-color: transparent;
@ -194,4 +194,11 @@ input::-webkit-inner-spin-button {
}
}
}
&.change{
height: 40px;
padding-left: 10px;
&::before{
display: none;
}
}
}

View File

@ -16,4 +16,10 @@
margin-top: 24px;
}
}
}
@media screen and (max-width: 360px){
.login-contents{
padding: 70px 34px 0;
}
}

View File

@ -88,4 +88,12 @@
}
}
}
}
@media screen and (max-width: 360px){
.main-grid-wrap{
.main-grid-bx{
padding: 40px 20px 20px;
}
}
}

View File

@ -33,4 +33,11 @@
.btn-flex-wrap{
margin-top: 20px;
}
}
// 지붕재 적합성 상세
.compliance-check-pop-contents{
padding: 14px;
border-top: 1px solid #ECECEC;
margin-top: 10px;
}

View File

@ -17,6 +17,10 @@
@include defaultFont($font-s-13, $font-w-400, #A8B6C7);
}
}
.data-input-guide{
margin-top: 8px;
@include defaultFont($font-s-13, $font-w-400, #A8B6C7);
}
}
.btn-flex-wrap{
@ -25,6 +29,14 @@
.btn-bx{
flex: 1;
}
&.com{
.btn-bx{
flex: 1 1 auto;
button{
font-size: 12px;
}
}
}
}
// 매물 common
@ -177,6 +189,13 @@
padding-left: 6px;
}
}
&.nodata{
.sale-item-nodata{
padding: 5px 0;
text-align: center;
@include defaultFont($font-s-15, $font-w-500, $font-c);
}
}
}
.sale-edit-btn{
margin-top: 24px;
@ -335,6 +354,14 @@
top: 0;
right: 0;
}
&.nodata{
padding-right: 0;
.inquiry-item-nodata{
padding: 10px 0;
text-align: center;
@include defaultFont($font-s-15, $font-w-500, $font-c);
}
}
}
}
}
@ -360,9 +387,12 @@
@include flex(0px);
align-items: center;
.file-item-name{
@include ellipsis(1);
@include defaultFont($font-s-13, $font-w-400, $font-c);
padding-right: 10px;
}
.file-del{
flex: none;
display: block;
margin-left: auto;
width: 16px;
@ -416,4 +446,177 @@
.inquiry-answer-date{
@include defaultFont($font-s-13, $font-w-400, #F86A56);
}
}
// 비밀번호 변경
.border-frame{
padding: 20px;
border-top: 1px solid #ECECEC;
border-bottom: 1px solid #ECECEC;
background-color: #fff;
margin-bottom: 10px;
&:last-child{
border-bottom: none;
padding-bottom: 0;
margin-bottom: 0;
}
}
.pw-guide{
.pw-guide-tit{
@include defaultFont($font-s-16, $font-w-500, #1259CB);
}
.pw-guide-txt{
@include defaultFont($font-s-13, $font-w-400, #417DDC);
}
}
// 지붕재 적합성
.compliance-icon{
display: block;
width: 22px;
height: 22px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
&.check{
background-image: url(/assets/images/sub/compliance_check_icon.svg);
}
&.x{
background-image: url(/assets/images/sub/compliance_x_icon.svg);
}
&.quest{
background-image: url(/assets/images/sub/compliance_quest_icon.svg);
}
&.tip{
background-image: url(/assets/images/sub/compliance_tip_icon.svg);
}
}
.compliance-check-wrap{
padding-top: 10px;
}
.compliance-check-bx{
position: relative;
padding: 14px 18px;
border: 1px solid #EFEFEF;
border-radius: 4px;
margin-bottom: 10px;
&:last-child{
margin-bottom: 0;
}
&.act{
.bx-btn{
transform: rotate(0) !important;
}
.reference-list{
display: block
}
}
}
.check-name-wrap{
@include flex(0px);
align-items: center;
.check-name{
@include defaultFont($font-s-13, $font-w-500, $font-c);
}
.check-name-btn{
margin-left: auto;
.bx-btn{
display: block;
width: 22px;
height: 22px;
background: url(/assets/images/sub/compliance_bx_icon.svg)no-repeat center;
transform: rotate(180deg);
}
}
}
.reference-list{
display: none;
margin-top: 10px;
padding-top: 14px;
border-top: 1px solid #ECECEC;
transition: all .15s ease-in-out;
.reference-item{
margin-bottom: 8px;
padding-left: 14px;
.reference-item-bx{
@include flex(10px);
@include defaultFont($font-s-13, $font-w-400, $font-c);
align-items: center;
}
&:last-child{
margin-bottom: 0;
}
}
&.check{
.reference-item{
margin-bottom: 14px;
}
}
}
.compliace-nodata{
margin-top: 24px;
padding: 30px 0;
@include defaultFont($font-s-13, $font-w-400, $font-c);
text-align: center;
}
.compliace-nosearch{
padding: 30px 0;
span{
display: block;
@include defaultFont($font-s-13, $font-w-400, $font-c);
text-align: center;
}
}
.check-form-box{
&.com-tit{
label{
&:before{
width: 20px;
height: 20px;
top: 0;
}
}
input[type="checkbox"]:checked + label{
font-weight: 500;
&::after{
left: -1px;
}
}
}
&.com-txt{
label{
@include defaultFont($font-s-13, $font-w-400, #8595A7);
&:before{
width: 20px;
height: 20px;
top: 0;
}
}
input[type="checkbox"]:checked + label{
&::after{
left: -1px;
}
}
}
}
.check-item-wrap{
display: flex;
align-items: center;
}
.compliance-icon-wrap{
margin-left: auto;
display: flex;
align-items: center;
}
@media screen and (max-width: 360px){
.btn-flex-wrap{
flex-direction: column;
}
.data-check-wrap{
.radio-form-box,
.check-form-box{
width: 100%;
}
}
}

View File

@ -31,6 +31,7 @@ $default-color: #2C2C2C;
.p-body{
margin-top: 30px;
padding-bottom: 70px;
flex: 1 1 auto;
.p-contents{
max-width: 1480px;

View File

@ -9,11 +9,11 @@ export interface SessionData {
storeId: null
userId: null
category: null
userNm: null
userNmKana: null
userNm: null | string
userNmKana: null | string
telNo: null
fax: null
email: null
email: null | string
lastEditUser: null
storeGubun: null
pwCurr: null

View File

@ -1,3 +1,5 @@
import { SEARCH_OPTIONS_ENUM, SEARCH_OPTIONS_PARTNERS_ENUM, SORT_OPTIONS_ENUM } from "@/store/surveyFilterStore"
export type SurveyBasicInfo = {
id: number
representative: string
@ -18,6 +20,7 @@ export type SurveyBasicInfo = {
export type SurveyDetailInfo = {
id: number
basic_info_id: number
contract_capacity: string | null
retail_company: string | null
supplementary_facilities: string | null // number 배열
@ -108,3 +111,7 @@ export type SurveyDetailRequest = {
installation_availability_etc: string | null
memo: string | null
}
export type SurveyDetailCoverRequest = {
detail_info: SurveyDetailRequest
}