diff --git a/.gitignore b/.gitignore index f167b61e8..ee2cfd052 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,7 @@ # dependencies node_modules/ - -# build -.next/ -dist/ +.idea/ # build .next @@ -22,5 +19,14 @@ apps/fo/.next/**/* #.env* .qodo +# next-agents-md +.next-docs/ + pnpm-lock.yaml -settings.json \ No newline at end of file +settings.json +.npmrc +.cursor +.agent + +apps/**/tsconfig.tsbuildinfo +logs/ diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000..2adeaead9 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,10 @@ +#!/bin/sh +BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$BRANCH" != "main" ]; then + exit 0 +fi +echo "Branch: $BRANCH" +echo "Changed packages (dry-run):" +pnpm turbo run build --filter="...[origin/$BRANCH]" --dry-run +echo "Starting build..." +pnpm turbo run build --filter="...[origin/$BRANCH]" diff --git a/apps/_template/.env.example b/apps/_template/.env.example new file mode 100644 index 000000000..f9794a6f8 --- /dev/null +++ b/apps/_template/.env.example @@ -0,0 +1,8 @@ +# apps/as/shared/config/env.ts 참고 +NEXT_PUBLIC_PORT= +NEXT_PUBLIC_PROXY_HOST= +NEXT_PUBLIC_SITE_ID= +NEXT_PUBLIC_SERVER_API_URL= + +# 401 시 브라우저 리다이렉트 대상 +NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL=/auth/login diff --git a/apps/_template/.eslintrc.json b/apps/_template/.eslintrc.json new file mode 100644 index 000000000..ea782d70a --- /dev/null +++ b/apps/_template/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/apps/_template/.gitignore b/apps/_template/.gitignore new file mode 100644 index 000000000..fd3dbb571 --- /dev/null +++ b/apps/_template/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/_template/.prettierrc b/apps/_template/.prettierrc new file mode 100644 index 000000000..059b4e33c --- /dev/null +++ b/apps/_template/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": false, + "printWidth": 100, + "tabWidth": 2, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/apps/_template/README.md b/apps/_template/README.md new file mode 100644 index 000000000..7e14528f8 --- /dev/null +++ b/apps/_template/README.md @@ -0,0 +1,248 @@ +# AS Application + +Next.js 애플리케이션 with FSD(Feature Sliced Design) 아키텍처 + +## 기술 스택 + +- **프레임워크**: Next.js 15 + TypeScript +- **스타일링**: Tailwind CSS +- **상태관리**: Zustand +- **데이터 페칭**: TanStack Query (React Query) +- **폼 관리**: React Hook Form + Zod +- **테이블**: TanStack Table +- **HTTP 클라이언트**: Axios +- **날짜 처리**: Day.js + +## 프로젝트 구조 (FSD) + +이 프로젝트는 **Feature-Sliced Design v2.1**을 따르며, 계층적 아키텍처로 구성되어 있습니다. + +``` +as/ +├── app/ # App 레이어 (Next.js App Router) +│ ├── (routes)/ # 라우트 구조 +│ ├── layout.tsx # 루트 레이아웃 +│ ├── page.tsx # 홈 페이지 +│ ├── globals.css # 글로벌 스타일 +│ └── providers.tsx # 전역 providers +│ +├── widgets/ # Widgets 레이어 +│ └── [widget-name]/ # 큰 독립적 UI 블록 (여러 페이지에서 재사용) +│ ├── ui/ +│ ├── model/ +│ └── index.ts +│ +├── features/ # Features 레이어 +│ └── [feature-name]/ # 사용자 상호작용 기능 (여러 페이지에서 재사용) +│ ├── ui/ +│ ├── api/ +│ ├── model/ +│ └── index.ts +│ +├── entities/ # Entities 레이어 +│ └── [entity-name]/ # 비즈니스 엔티티 (User, Product 등) +│ ├── model/ +│ ├── api/ +│ ├── ui/ +│ └── index.ts +│ +└── shared/ # Shared 레이어 + ├── ui/ # UI Kit (비즈니스 로직 없는 공통 컴포넌트) + ├── api/ # API 클라이언트 설정 + ├── lib/ # 내부 라이브러리 (dates, colors 등) + ├── config/ # 환경 설정, feature flags + ├── types/ # 공통 타입 정의 + └── routes/ # 라우트 상수 +``` + +### 계층 구조 이해하기 + +FSD는 **3단계 계층**으로 구성됩니다: + +1. **Layers (레이어)**: 책임과 의존성에 따른 수평적 분리 (app, pages, widgets, features, entities, shared) +2. **Slices (슬라이스)**: 비즈니스 도메인에 따른 수직적 분리 (user, product, auth-login 등) +3. **Segments (세그먼트)**: 기술적 목적에 따른 분리 (ui, api, model, lib 등) + +> 📚 각 레이어의 자세한 설명은 해당 폴더의 README.md를 참고하세요. + +## FSD 레이어 설명 + +FSD는 계층적 아키텍처로, 각 레이어는 책임의 범위와 의존성의 정도에 따라 구분됩니다. 7개의 표준화된 레이어가 있으며, 프로젝트에 필요한 레이어만 사용하면 됩니다. + +### 레이어 계층 구조 (위에서 아래로) + +``` +app/ ↑ 가장 높은 책임, 가장 많은 의존성 +pages/ | +widgets/ | +features/ | +entities/ | +shared/ ↓ 가장 낮은 책임, 의존성 없음 +``` + +### 1. App +- **목적**: 앱 전체에 영향을 주는 기술적/비즈니스적 사항 +- **내용**: Context providers, 글로벌 스타일, 스토어 설정, 라우터 설정, 애널리틱스 +- **특징**: 슬라이스가 없고 세그먼트로만 구성 +- **의존성**: 모든 하위 레이어 사용 가능 + +### 2. Pages (app 디렉토리) +- **목적**: 웹사이트나 애플리케이션을 구성하는 페이지/화면 +- **내용**: 페이지 UI, 로딩 상태, 에러 바운더리, 데이터 fetching +- **특징**: 한 페이지당 한 슬라이스 (유사한 페이지는 그룹화 가능) +- **의존성**: `widgets`, `features`, `entities`, `shared` 사용 가능 + +> 💡 **FSD v2.1 권장사항**: "pages-first" 접근 방식을 따라, 재사용되지 않는 큰 UI 블록과 비즈니스 로직은 페이지 슬라이스에 유지하세요. + +### 3. Widgets +- **목적**: 큰 규모의 독립적인 UI 블록 +- **내용**: 여러 features와 entities를 조합한 복합 UI +- **사용 시기**: 여러 페이지에서 재사용되거나, 페이지에 여러 개의 큰 독립적 블록이 있을 때 +- **의존성**: `features`, `entities`, `shared` 사용 가능 (다른 `widgets` 불가) + +### 4. Features +- **목적**: 사용자가 수행하는 주요 상호작용과 비즈니스 기능 +- **내용**: 사용자 시나리오 (예: 로그인, 상품 검색, 댓글 작성) +- **사용 시기**: 여러 페이지에서 재사용되는 기능 +- **의존성**: `entities`, `shared` 사용 가능 (다른 `features` 불가) + +### 5. Entities +- **목적**: 비즈니스가 다루는 실제 개념 (User, Product, Order 등) +- **내용**: 데이터 모델, 타입, API 요청, 엔티티의 시각적 표현 +- **특징**: 비즈니스 용어로 명명 +- **의존성**: 다른 `entities`, `shared` 사용 가능 + +### 6. Shared +- **목적**: 애플리케이션의 기반 제공 +- **내용**: API 클라이언트, UI Kit, 유틸리티 라이브러리, 글로벌 설정 +- **특징**: 비즈니스 로직 없음, 슬라이스가 없고 세그먼트로만 구성 +- **의존성**: 외부 라이브러리만 사용 가능 (다른 레이어 불가) + +## 레이어 간 Import 규칙 + +> **핵심 규칙**: 슬라이스 내의 모듈(파일)은 **오직 하위 레이어**에 있는 다른 슬라이스만 import할 수 있습니다. + +예시: +- `features/auth-login/ui/form.tsx`는 `entities/user`와 `shared/ui`를 import 가능 +- 하지만 다른 `features/post-create`나 상위 레이어인 `widgets`는 import 불가 +- 같은 슬라이스 내부(`features/auth-login/`)의 다른 파일들은 자유롭게 import 가능 + +## Public API 규칙 + +> **슬라이스 외부의 모듈은 슬라이스의 Public API만 참조할 수 있으며, 내부 파일 구조에 직접 접근할 수 없습니다.** + +모든 슬라이스(및 슬라이스가 없는 레이어의 세그먼트)는 반드시 `index.ts`를 통해 Public API를 정의해야 합니다. + +```typescript +// ✅ 올바른 import +import { LoginForm } from 'features/auth-login'; + +// ❌ 잘못된 import (내부 구조 직접 접근) +import { LoginForm } from 'features/auth-login/ui/login-form'; +``` + +## 시작하기 + +```bash +# 의존성 설치 +pnpm install + +# 개발 서버 실행 +pnpm run dev + +# 프로덕션 빌드 +pnpm run build + +# 프로덕션 서버 실행 +pnpm start +``` + +## 환경 변수 + +`.env.local` 파일 생성: + +```env +NEXT_PUBLIC_API_URL=http://localhost:3000/api +``` + +## FSD 핵심 규칙 + +### 1. Import 규칙 + +> **슬라이스 내의 모듈(파일)은 오직 하위 레이어에 있는 슬라이스만 import할 수 있습니다.** + +```typescript +// ✅ 올바른 import (하위 레이어) +// features/auth-login/ui/form.tsx +import { User } from 'entities/user'; +import { Button } from 'shared/ui/button'; + +// ❌ 잘못된 import (같은 레이어) +// features/auth-login/ui/form.tsx +import { PostForm } from 'features/post-create'; // 금지! + +// ❌ 잘못된 import (상위 레이어) +// features/auth-login/ui/form.tsx +import { Header } from 'widgets/header'; // 금지! +``` + +### 2. Public API 규칙 + +> **슬라이스 외부에서는 Public API(index.ts)를 통해서만 접근해야 합니다.** + +```typescript +// ✅ 올바른 import (Public API 사용) +import { LoginForm } from 'features/auth-login'; + +// ❌ 잘못된 import (내부 구조 직접 접근) +import { LoginForm } from 'features/auth-login/ui/login-form'; +``` + +**Public API 작성 시 주의사항**: +- 와일드카드 re-export(`export * from`) 사용 지양 +- 필요한 것만 명시적으로 export +- 슬라이스의 인터페이스를 명확하게 표현 + +```typescript +// ❌ 나쁜 예시 +export * from './ui/Comment'; +export * from './model/comments'; + +// ✅ 좋은 예시 +export { Comment } from './ui/comment'; +export { CommentForm } from './ui/comment-form'; +export { useComments } from './model/use-comments'; +export type { Comment, CommentFormData } from './model/types'; +``` + +### 3. 슬라이스 독립성 + +- **Zero Coupling**: 같은 레이어의 슬라이스들은 서로 독립적이어야 합니다 +- **High Cohesion**: 슬라이스는 주요 목표와 관련된 대부분의 코드를 포함해야 합니다 +- 예외: Entities 레이어에서 필요한 경우 `@x` 표기법으로 cross-import 가능 + +### 4. 세그먼트 명명 규칙 + +세그먼트 이름은 **목적**을 설명해야 하며, **본질**을 설명하면 안 됩니다: + +- ❌ **나쁜 이름**: `components`, `hooks`, `types` (무엇인지만 설명) +- ✅ **좋은 이름**: `ui`, `api`, `model`, `lib` (왜 있는지 설명) + +## 코드 규칙 + +- TypeScript 사용 (strict mode) +- 함수형 컴포넌트 사용 +- Named exports 선호 +- Tailwind CSS 사용 +- ESLint + Prettier 적용 +- Public API는 명시적으로 정의 (와일드카드 export 지양) + +## 아키텍처 린팅 + +코드베이스의 FSD 규칙 준수를 자동으로 검증하려면 [Steiger](https://github.com/feature-sliced/steiger)를 사용하세요. + +```bash +# Steiger 설치 및 실행 +pnpm add -D @feature-sliced/steiger +npx steiger src +``` diff --git a/apps/_template/app/(with-base-layout)/layout.tsx b/apps/_template/app/(with-base-layout)/layout.tsx new file mode 100644 index 000000000..1d182a0ee --- /dev/null +++ b/apps/_template/app/(with-base-layout)/layout.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useCallback } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; +import { BaseLayout } from "@common/components"; +import type { MenuItem } from "@/entities/menu"; +import { PageLayout } from "@/shared/ui/page-layout"; + +export default function WithBaseLayout({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const pathname = usePathname(); + const queryClient = useQueryClient(); + + const handleMenuClick = useCallback( + (item: MenuItem) => { + router.push(item.urlPath); + }, + [router], + ); + + return ( + + {children} + + ); +} diff --git a/apps/_template/app/(with-base-layout)/page.tsx b/apps/_template/app/(with-base-layout)/page.tsx new file mode 100644 index 000000000..e118e732e --- /dev/null +++ b/apps/_template/app/(with-base-layout)/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +export default function MainPage() { + return ( +
+

Home

+
+ ); +} diff --git a/apps/_template/app/README.md b/apps/_template/app/README.md new file mode 100644 index 000000000..5ab63ea34 --- /dev/null +++ b/apps/_template/app/README.md @@ -0,0 +1,169 @@ +# App Layer + +**App**은 애플리케이션 전체에 영향을 미치는 기술적, 비즈니스적 사항을 다루는 최상위 레이어입니다. + +## 목적 + +- 애플리케이션의 진입점(entrypoint) 제공 +- 전역 Context Providers 설정 +- 라우터 구성 +- 글로벌 스타일 정의 +- 전역 상태 저장소 설정 +- 애널리틱스 및 모니터링 설정 + +## 특징 + +- **슬라이스 없음**: App 레이어는 Shared처럼 슬라이스가 없고 세그먼트로만 구성됩니다 +- **전체 앱 관심사**: 특정 비즈니스 도메인이 아닌 앱 전체와 관련된 코드만 포함 +- **최상위 레이어**: 모든 하위 레이어의 모듈을 자유롭게 import 가능 + +## 구조 (Next.js App Router) + +Next.js를 사용하는 경우, App 레이어는 주로 `app/` 디렉토리로 표현됩니다: + +``` +app/ +├── (routes)/ # 라우트 그룹 및 페이지 +│ ├── (auth)/ +│ │ ├── login/ +│ │ │ └── page.tsx +│ │ └── register/ +│ │ └── page.tsx +│ └── (main)/ +│ ├── dashboard/ +│ │ └── page.tsx +│ └── settings/ +│ └── page.tsx +├── layout.tsx # 루트 레이아웃 +├── page.tsx # 홈 페이지 +├── globals.css # 글로벌 스타일 +├── providers.tsx # 전역 Providers 설정 +└── error.tsx # 글로벌 에러 핸들러 +``` + +### 일반적인 FSD 세그먼트 구조 + +FSD 표준 세그먼트를 사용하는 경우: + +``` +app/ +├── entrypoint/ # 애플리케이션 진입점 (프레임워크별) +│ └── index.tsx +├── providers/ # Context Providers +│ ├── query-provider.tsx +│ ├── theme-provider.tsx +│ └── index.tsx +├── styles/ # 글로벌 스타일 +│ ├── globals.css +│ └── theme.css +├── store/ # 글로벌 상태 저장소 설정 +│ └── index.ts +└── routes/ # 라우터 설정 + └── index.tsx +``` + +## 예시 + +### Next.js Providers 설정 + +```typescript +// app/providers.tsx +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ThemeProvider } from 'next-themes'; +import { useState } from 'react'; + +export function Providers({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + })); + + return ( + + + {children} + + + ); +} + +// app/layout.tsx +import { Providers } from './providers'; +import './globals.css'; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + ); +} +``` + +### 글로벌 설정 + +```typescript +// app/config/analytics.ts +export function initAnalytics() { + if (typeof window !== 'undefined') { + // 애널리틱스 초기화 + } +} + +// app/config/monitoring.ts +export function initMonitoring() { + // 에러 모니터링 설정 +} +``` + +## Next.js App Router와 FSD + +Next.js의 `app/` 디렉토리는 FSD의 **App 레이어**와 **Pages 레이어**를 동시에 담당합니다: + +- **루트 레벨 파일들** (`layout.tsx`, `providers.tsx`, `globals.css`): App 레이어 +- **라우트 폴더들** (`dashboard/`, `login/` 등): Pages 레이어 + +각 페이지별 비즈니스 로직은: +- 재사용되지 않는 UI는 페이지 슬라이스(`app/dashboard/` 등) 안에 유지 +- 재사용되는 기능은 `features/` 레이어로 분리 +- 여러 페이지에서 재사용되는 큰 UI 블록은 `widgets/` 레이어로 분리 + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- 모든 하위 레이어 (`pages`, `widgets`, `features`, `entities`, `shared`) +- 같은 `app` 레이어 내의 다른 세그먼트 +- 외부 라이브러리 + +❌ **금지된 의존성**: +- 없음 (App은 최상위 레이어) + +## 주의사항 + +1. **비대화 방지**: App 레이어가 너무 커지면 관리가 어려워집니다. 대부분의 로직은 하위 레이어에 배치하세요 +2. **전역 관심사만**: 특정 페이지나 기능에만 관련된 코드는 해당 레이어로 이동 +3. **라우터 레벨 로직**: 라우터 및 서버 레벨 로직은 이 레이어에 유지 +4. **Processes 레이어 사용 지양**: Processes 레이어는 deprecated되었으므로 해당 로직은 `app`이나 `features`에 배치 + +## FSD v2.1 권장사항 + +최신 FSD 버전(v2.1)은 **"pages-first"** 접근 방식을 권장합니다: + +- 더 많은 코드를 페이지 슬라이스에 유지 +- 큰 UI 블록과 재사용되지 않는 비즈니스 로직은 즉시 추출하지 말고 페이지에 보관 +- 실제로 재사용이 필요할 때만 `features`나 `widgets`로 분리 + +이 접근 방식은 과도한 추상화를 방지하고 코드를 더 쉽게 탐색할 수 있게 합니다. diff --git a/apps/_template/app/globals.css b/apps/_template/app/globals.css new file mode 100644 index 000000000..60bc92fb6 --- /dev/null +++ b/apps/_template/app/globals.css @@ -0,0 +1,15 @@ +@import "@common/assets/styles/base.css"; +@import "@common/assets/styles/diveicon.css"; +@import "@common/components/index.css"; + +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + color: var(--foreground); + background: var(--background); + font-family: var(--font-noto-sans-kr), sans-serif; + font-size: 10px; +} diff --git a/apps/_template/app/layout.tsx b/apps/_template/app/layout.tsx new file mode 100644 index 000000000..d152e0da3 --- /dev/null +++ b/apps/_template/app/layout.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from "next"; +import { Noto_Sans_KR } from "next/font/google"; +import { Providers } from "./providers"; +import "./globals.css"; + +const notoSansKR = Noto_Sans_KR({ + subsets: ["latin"], + weight: ["100", "300", "400", "500", "700", "900"], + display: "swap", + variable: "--font-noto-sans-kr", +}); + +export const metadata: Metadata = { + title: "행정지원시스템", + description: "행정지원시스템", +}; + +interface RootLayoutProps { + children: React.ReactNode; +} + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + + {children} + + + ); +} diff --git a/apps/_template/app/providers.tsx b/apps/_template/app/providers.tsx new file mode 100644 index 000000000..bc12c443b --- /dev/null +++ b/apps/_template/app/providers.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { queryClient } from "@/shared/lib"; + +interface ProvidersProps { + children: React.ReactNode; +} + +export function Providers({ children }: ProvidersProps) { + return ( + + {children} + + + ); +} diff --git a/apps/_template/entities/README.md b/apps/_template/entities/README.md new file mode 100644 index 000000000..b28c10790 --- /dev/null +++ b/apps/_template/entities/README.md @@ -0,0 +1,114 @@ +# Entities Layer + +**Entities**는 프로젝트가 다루는 실제 비즈니스 개념을 나타내는 레이어입니다. 비즈니스에서 제품을 설명할 때 사용하는 용어들(예: User, Product, Order)이 주로 엔티티가 됩니다. + +## 목적 + +- 비즈니스 엔티티의 데이터 모델 및 타입 정의 +- 엔티티 관련 API 요청 함수 관리 +- 엔티티의 시각적 표현 제공 (재사용 가능한 UI) +- 여러 페이지에서 동일한 외관을 재사용할 수 있도록 지원 + +## 구조 + +각 엔티티 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +entities/ +└── [entity-name]/ + ├── model/ # 데이터 저장소, 검증 스키마, 상태 관리 + ├── api/ # 엔티티 관련 API 요청 함수 + ├── ui/ # 엔티티 시각적 표현 (선택적) + ├── lib/ # 엔티티 관련 유틸리티 (선택적) + ├── @x/ # 다른 엔티티로부터의 cross-import용 Public API (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **model**: 데이터 모델, 타입 정의, 상태 저장소 (Zustand 등) +- **api**: 백엔드 API 호출 함수 +- **ui**: 엔티티를 표시하는 UI 컴포넌트 (완전한 UI 블록이 아니라 재사용 가능한 표현) +- **lib**: 엔티티 관련 유틸리티 함수 +- **@x**: 다른 엔티티가 이 엔티티를 import할 때 사용하는 명시적 cross-import API +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// entities/user/model/user.ts +export interface User { + id: string; + name: string; + email: string; + role: 'admin' | 'user'; +} + +// entities/user/api/get-user.ts +import type { User } from '../model/user'; + +export async function getUser(id: string): Promise { + const response = await fetch(`/api/users/${id}`); + return response.json(); +} + +// entities/user/ui/user-card.tsx +import type { User } from '../model/user'; + +interface UserCardProps { + user: User; + onClick?: () => void; +} + +export function UserCard({ user, onClick }: UserCardProps) { + return ( +
+

{user.name}

+

{user.email}

+
+ ); +} + +// entities/user/index.ts +export { type User } from './model/user'; +export { getUser } from './api/get-user'; +export { UserCard } from './ui/user-card'; +``` + +## 엔티티 간 관계 + +엔티티가 서로 연결되어 있을 때는 `@x` 표기법을 사용하여 명시적으로 cross-import를 관리하는 것이 권장됩니다: + +```typescript +// entities/artist/model/artist.ts +import type { Song } from 'entities/song/@x/artist'; + +export interface Artist { + name: string; + songs: Array; +} + +// entities/song/@x/artist.ts +export type { Song } from '../model/song'; +``` + +> `@x` 폴더를 사용하면 엔티티 간 연결을 명확하게 만들어 함께 리팩터링이 필요한 코드를 쉽게 찾을 수 있습니다. + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- 같은 엔티티 슬라이스 내의 다른 세그먼트 +- 다른 엔티티 슬라이스 (필요한 경우, `@x` 표기법 권장) + +❌ **금지된 의존성**: +- `features` 레이어 +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +## 주의사항 + +1. **UI 컴포넌트는 완전한 비즈니스 로직을 포함하지 않음**: UI는 주로 props나 slots를 통해 다양한 비즈니스 로직을 연결할 수 있도록 설계 +2. **엔티티 간 비즈니스 로직은 상위 레이어에**: 엔티티 간 상호작용의 비즈니스 로직은 Features나 Pages 레이어에서 처리 +3. **데이터 관리에 집중**: 복잡한 비즈니스 로직보다는 데이터 모델, 검증, 저장에 집중 diff --git a/apps/_template/entities/menu/index.ts b/apps/_template/entities/menu/index.ts new file mode 100644 index 000000000..cb55d9a1c --- /dev/null +++ b/apps/_template/entities/menu/index.ts @@ -0,0 +1 @@ +export { type MenuItem } from "./model/types"; diff --git a/apps/_template/entities/menu/model/types.ts b/apps/_template/entities/menu/model/types.ts new file mode 100644 index 000000000..9692821e3 --- /dev/null +++ b/apps/_template/entities/menu/model/types.ts @@ -0,0 +1,19 @@ +export interface MenuItem { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: MenuItem[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; +} diff --git a/apps/_template/eslint.config.mjs b/apps/_template/eslint.config.mjs new file mode 100644 index 000000000..05e726d1b --- /dev/null +++ b/apps/_template/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/apps/_template/features/README.md b/apps/_template/features/README.md new file mode 100644 index 000000000..2be9a3e17 --- /dev/null +++ b/apps/_template/features/README.md @@ -0,0 +1,130 @@ +# Features Layer + +**Features**는 앱의 주요 사용자 상호작용과 기능을 구현하는 레이어입니다. 사용자가 실제로 수행하는 작업들로, 주로 비즈니스 엔티티와 관련이 있습니다. + +## 목적 + +- 사용자가 중요하게 생각하는 주요 상호작용 구현 +- 재사용 가능한 비즈니스 기능 제공 +- 여러 페이지에서 사용되는 기능의 중복 제거 + +## Feature로 만들어야 하는 경우 + +> ⚠️ **중요**: 모든 것을 feature로 만들 필요는 없습니다. + +Feature로 만드는 것이 좋은 지표: +- **여러 페이지에서 재사용됨**: 예를 들어, 여러 에디터가 있고 모두 댓글 기능이 있다면, 댓글은 재사용 가능한 feature입니다 +- **중요한 비즈니스 기능**: 신규 프로젝트 참여자가 Features 폴더를 보고 앱의 주요 기능을 빠르게 파악할 수 있어야 합니다 + +Feature가 너무 많으면 중요한 기능이 묻히므로, 신규 참여자의 경험을 최적화하여 결정하세요. + +## 구조 + +각 feature 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +features/ +└── [feature-name]/ + ├── ui/ # 상호작용을 수행하는 UI (폼, 버튼 등) + ├── api/ # 액션 수행에 필요한 API 호출 (선택적) + ├── model/ # 검증 및 내부 상태 관리 (선택적) + ├── lib/ # 유틸리티 함수 (선택적) + ├── config/ # feature flags (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **ui**: 사용자 상호작용을 위한 UI 컴포넌트 (폼, 모달, 버튼 등) +- **api**: feature 실행에 필요한 API 요청 함수 +- **model**: 폼 검증, 내부 상태, 비즈니스 로직 +- **lib**: feature 내부에서만 사용되는 유틸리티 +- **config**: feature flags, feature별 설정값 +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// features/auth-login/ui/login-form.tsx +import { useState } from 'react'; +import { loginUser } from '../api/login-user'; +import type { LoginCredentials } from '../model/types'; + +export function LoginForm() { + const [credentials, setCredentials] = useState({ + email: '', + password: '' + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await loginUser(credentials); + }; + + return ( +
+ setCredentials({ ...credentials, email: e.target.value })} + /> + setCredentials({ ...credentials, password: e.target.value })} + /> + +
+ ); +} + +// features/auth-login/api/login-user.ts +import type { LoginCredentials } from '../model/types'; + +export async function loginUser(credentials: LoginCredentials) { + const response = await fetch('/api/auth/login', { + method: 'POST', + body: JSON.stringify(credentials), + }); + return response.json(); +} + +// features/auth-login/model/types.ts +export interface LoginCredentials { + email: string; + password: string; +} + +// features/auth-login/index.ts +export { LoginForm } from './ui/login-form'; +export { loginUser } from './api/login-user'; +export type { LoginCredentials } from './model/types'; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- `entities` 레이어의 모듈 +- 같은 feature 슬라이스 내의 다른 세그먼트 + +❌ **금지된 의존성**: +- 다른 `features` 슬라이스 (feature 간 직접 의존 금지) +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +> 💡 **팁**: 여러 features가 공통 기능을 필요로 한다면, 그 기능을 `shared` 레이어로 이동시키거나 `entities` 레이어에서 관리하세요. + +## 명명 규칙 + +- 소문자와 대시 사용: `auth-login`, `post-create`, `comment-edit` +- 동사 형태 권장: 사용자 액션을 나타내는 이름 사용 +- 명확하고 구체적으로: 기능의 목적이 명확하게 드러나도록 + +## 주의사항 + +1. **하나의 기능에만 집중**: 각 feature는 단일 사용자 시나리오를 구현해야 합니다 +2. **재사용성 고려**: 여러 페이지에서 사용될 수 있도록 설계 +3. **독립성 유지**: 다른 features에 의존하지 않고 독립적으로 작동 +4. **적절한 추상화**: 너무 많은 feature를 만들지 말고, 중요한 기능 위주로 구성 diff --git a/apps/_template/next.config.ts b/apps/_template/next.config.ts new file mode 100644 index 000000000..8864de0e7 --- /dev/null +++ b/apps/_template/next.config.ts @@ -0,0 +1,8 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + transpilePackages: ["@common/assets"], +}; + +export default nextConfig; diff --git a/apps/_template/package.json b/apps/_template/package.json new file mode 100644 index 000000000..fb27f8af7 --- /dev/null +++ b/apps/_template/package.json @@ -0,0 +1,36 @@ +{ + "name": "as", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "lint:fsd": "steiger ." + }, + "dependencies": { + "@common/assets": "workspace:*", + "@common/components": "workspace:*", + "@common/network": "workspace:*", + "@hookform/resolvers": "^4.1.3", + "@tanstack/react-query": "^5.75.5", + "@tanstack/react-query-devtools": "^5.75.5", + "@tanstack/react-table": "^8.21.2", + "axios": "^1.8.1", + "dayjs": "^1.11.13", + "next": "15.3.8", + "react-hook-form": "^7.54.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "eslint": "^9.0.0", + "eslint-config-next": "15.4.0", + "postcss": "^8.4.49", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "steiger": "^0.5.11", + "tailwindcss": "^3.4.14", + "typescript": "^5.7.2" + } +} \ No newline at end of file diff --git a/apps/_template/postcss.config.mjs b/apps/_template/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/apps/_template/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/_template/shared/README.md b/apps/_template/shared/README.md new file mode 100644 index 000000000..2b247021d --- /dev/null +++ b/apps/_template/shared/README.md @@ -0,0 +1,149 @@ +# Shared Layer + +**Shared**는 애플리케이션의 기반을 형성하는 레이어입니다. 외부 세계(백엔드, 서드파티 라이브러리, 환경)와의 연결을 생성하고, 높은 응집력을 가진 자체 라이브러리를 정의하는 곳입니다. + +## 목적 + +- 외부 시스템 및 라이브러리와의 통합 +- 프로젝트 전체에서 사용되는 공통 코드 제공 +- 비즈니스 로직이 없는 재사용 가능한 유틸리티 및 UI 제공 +- 글로벌 설정 및 환경 변수 관리 + +## 특징 + +- **슬라이스 없음**: Shared 레이어는 비즈니스 도메인이 없으므로 슬라이스로 나뉘지 않습니다 +- **세그먼트 구성**: 기술적 목적에 따라 세그먼트로 직접 구성됩니다 +- **자유로운 import**: Shared 내의 모든 파일은 서로를 참조하고 import할 수 있습니다 + +## 구조 + +``` +shared/ +├── ui/ # 애플리케이션 UI Kit +│ ├── button/ +│ ├── input/ +│ ├── modal/ +│ └── index.ts +├── api/ # API 클라이언트 및 백엔드 엔드포인트 함수 +│ ├── client.ts +│ └── index.ts +├── lib/ # 내부 라이브러리 모음 +│ ├── dates/ # 날짜 관련 유틸리티 +│ ├── colors/ # 색상 관련 유틸리티 +│ ├── text/ # 텍스트 처리 유틸리티 +│ └── index.ts +├── config/ # 환경 변수, 글로벌 feature flags +│ ├── env.ts +│ └── index.ts +├── types/ # 공통 타입 정의 +│ └── index.ts +├── i18n/ # 번역 설정, 글로벌 번역 문자열 +│ └── index.ts +└── routes/ # 라우트 상수 또는 패턴 + └── index.ts +``` + +## 세그먼트 설명 + +### 📁 ui +애플리케이션의 UI Kit로, 비즈니스 로직을 포함하지 않는 재사용 가능한 UI 컴포넌트들입니다. + +- Button, Input, Modal 같은 기본 컴포넌트 +- 비즈니스 로직은 없지만 UI 로직은 포함 가능 (예: Autocomplete, SearchBar) +- 비즈니스 테마 컴포넌트도 허용 (예: 회사 로고, 페이지 레이아웃) + +### 📁 api +API 클라이언트 및 특정 백엔드 엔드포인트 요청 함수입니다. + +- HTTP 클라이언트 설정 (Axios, Fetch 등) +- 인증 인터셉터 +- 공통 에러 핸들링 + +### 📁 lib +내부 라이브러리 모음으로, 각 라이브러리는 **하나의 관심사**에 집중해야 합니다. + +> ⚠️ **중요**: `helpers`나 `utils` 폴더처럼 덤프 폴더로 만들지 마세요. 각 라이브러리는 명확한 초점 영역(예: dates, colors, text)을 가져야 하며, README로 문서화해야 합니다. + +### 📁 config +환경 변수, 글로벌 feature flags 및 앱 전체 설정입니다. + +### 📁 types +프로젝트 전체에서 사용되는 공통 타입 정의입니다. + +### 📁 i18n +번역 설정 코드 및 글로벌 번역 문자열입니다. + +### 📁 routes +라우트 상수 또는 라우트 매칭 패턴입니다. + +## 예시 + +```typescript +// shared/ui/button/button.tsx +interface ButtonProps { + children: React.ReactNode; + variant?: 'primary' | 'secondary'; + onClick?: () => void; +} + +export function Button({ children, variant = 'primary', onClick }: ButtonProps) { + return ( + + ); +} + +// shared/api/client.ts +import axios from 'axios'; + +export const apiClient = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + timeout: 10000, +}); + +// shared/lib/dates/format-date.ts +import dayjs from 'dayjs'; + +export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string { + return dayjs(date).format(format); +} + +// shared/config/env.ts +export const ENV = { + apiUrl: process.env.NEXT_PUBLIC_API_URL!, + isDev: process.env.NODE_ENV === 'development', +} as const; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- 같은 `shared` 레이어 내의 다른 세그먼트 (자유로운 cross-import) +- 외부 라이브러리 및 패키지 + +❌ **금지된 의존성**: +- `entities` 레이어 +- `features` 레이어 +- `widgets` 레이어 +- `pages` 레이어 +- `app` 레이어 + +> 💡 **원칙**: Shared는 가장 낮은 레이어이므로 상위 레이어에 의존하지 않습니다. + +## 세그먼트 명명 규칙 + +세그먼트 이름은 **내용의 목적**을 설명해야 하며, **본질**을 설명하면 안 됩니다. + +❌ **나쁜 예시**: `components`, `hooks`, `types` (본질을 설명) +✅ **좋은 예시**: `ui`, `api`, `lib/dates`, `config` (목적을 설명) + +## 주의사항 + +1. **비즈니스 로직 없음**: Shared는 순수한 기술적 코드만 포함 +2. **높은 재사용성**: 앱 전체에서 사용될 수 있어야 함 +3. **명확한 라이브러리 구조**: `lib` 내부는 관심사별로 명확하게 분리 +4. **문서화**: 각 라이브러리는 README로 사용법과 목적을 문서화 diff --git a/apps/_template/shared/api/client.ts b/apps/_template/shared/api/client.ts new file mode 100644 index 000000000..d7c4a8f1d --- /dev/null +++ b/apps/_template/shared/api/client.ts @@ -0,0 +1,9 @@ +import { Client } from "@common/network"; +import { PROXY_HOST, SITE_ID } from "@/shared/config/env"; + +const BASE_URL = `${PROXY_HOST}/server`; + +export const apiClient = Client.getInstance({ + baseURL: BASE_URL, + siteId: SITE_ID, +}); diff --git a/apps/_template/shared/api/index.ts b/apps/_template/shared/api/index.ts new file mode 100644 index 000000000..c5eb98066 --- /dev/null +++ b/apps/_template/shared/api/index.ts @@ -0,0 +1 @@ +export { apiClient } from "./client"; diff --git a/apps/_template/shared/config/env.ts b/apps/_template/shared/config/env.ts new file mode 100644 index 000000000..4f08c722a --- /dev/null +++ b/apps/_template/shared/config/env.ts @@ -0,0 +1,11 @@ +export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; +export const PROXY_HOST = + process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; +export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; +export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "AS"; +export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +export const ACCESS_TOKEN = + process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; +export const REFRESH_TOKEN = + process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token"; +export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; diff --git a/apps/_template/shared/config/index.ts b/apps/_template/shared/config/index.ts new file mode 100644 index 000000000..c76f8cd47 --- /dev/null +++ b/apps/_template/shared/config/index.ts @@ -0,0 +1 @@ +export * from "./env"; diff --git a/apps/_template/shared/lib/dayjs.ts b/apps/_template/shared/lib/dayjs.ts new file mode 100644 index 000000000..28b3f908b --- /dev/null +++ b/apps/_template/shared/lib/dayjs.ts @@ -0,0 +1,12 @@ +import dayjs from "dayjs"; +import "dayjs/locale/ko"; +import utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; +import relativeTime from "dayjs/plugin/relativeTime"; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(relativeTime); +dayjs.locale("ko"); + +export { dayjs }; diff --git a/apps/_template/shared/lib/index.ts b/apps/_template/shared/lib/index.ts new file mode 100644 index 000000000..51e9c2c5d --- /dev/null +++ b/apps/_template/shared/lib/index.ts @@ -0,0 +1,2 @@ +export { queryClient } from "./query-client"; +export { dayjs } from "./dayjs"; diff --git a/apps/_template/shared/lib/query-client.ts b/apps/_template/shared/lib/query-client.ts new file mode 100644 index 000000000..0da2fb899 --- /dev/null +++ b/apps/_template/shared/lib/query-client.ts @@ -0,0 +1,11 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + refetchOnWindowFocus: false, + staleTime: 60 * 1000, + }, + }, +}); diff --git a/apps/_template/shared/model/index.ts b/apps/_template/shared/model/index.ts new file mode 100644 index 000000000..15ddf4254 --- /dev/null +++ b/apps/_template/shared/model/index.ts @@ -0,0 +1,29 @@ +export interface BaseEntity { + id: string; + createdAt: string; + updatedAt: string; +} + +export interface PaginationParams { + page: number; + limit: number; +} + +export interface PaginationResponse { + data: T[]; + total: number; + page: number; + limit: number; +} + +export interface ApiResponse { + success: boolean; + data: T; + message?: string; +} + +export interface ApiError { + message: string; + code?: string; + status?: number; +} diff --git a/apps/_template/shared/ui/index.ts b/apps/_template/shared/ui/index.ts new file mode 100644 index 000000000..460542995 --- /dev/null +++ b/apps/_template/shared/ui/index.ts @@ -0,0 +1 @@ +export { PageLayout } from "./page-layout"; diff --git a/apps/_template/shared/ui/page-layout.tsx b/apps/_template/shared/ui/page-layout.tsx new file mode 100644 index 000000000..ecefc36bd --- /dev/null +++ b/apps/_template/shared/ui/page-layout.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { PageTitle } from "@common/components"; + +const mockCurrentMenuInfo = { + menuCd: 0, + menuKornNm: "메인", + menuEngNm: "Main", + parentId: null, + sortSeq: 0, + children: null, + levelNo: 0, + urlPath: "/", + menuLnkgTypeCd: "", + menuLnkgSn: 0, + menuPath: "메인", + qrcd: "", + useYn: "Y", + otptYn: "Y", + newWndwYn: "N", + requiredUrlPath: false, +}; + +function PageLayout({ children }: { children: React.ReactNode }) { + const menuPaths = mockCurrentMenuInfo.menuPath.split(">").map((path) => path.trim()); + + return ( +
+ + {children} +
+ ); +} + +export { PageLayout }; diff --git a/apps/_template/tailwind.config.ts b/apps/_template/tailwind.config.ts new file mode 100644 index 000000000..c02405aca --- /dev/null +++ b/apps/_template/tailwind.config.ts @@ -0,0 +1,29 @@ +import type { Config } from "tailwindcss"; +import presetConfig from "../../tailwind.config.preset"; + +const config: Config = { + presets: [presetConfig], + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./shared/**/*.{js,ts,jsx,tsx,mdx}", + "./entities/**/*.{js,ts,jsx,tsx,mdx}", + "./features/**/*.{js,ts,jsx,tsx,mdx}", + "./widgets/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "../../packages/common/components/dist/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + fontFamily: { + sans: ["var(--font-noto-sans-kr)", "Noto Sans KR", "sans-serif"], + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/apps/_template/tsconfig.json b/apps/_template/tsconfig.json new file mode 100644 index 000000000..15b023415 --- /dev/null +++ b/apps/_template/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"], + "@/app/*": ["./app/*"], + "@/shared/*": ["./shared/*"], + "@/entities/*": ["./entities/*"], + "@/features/*": ["./features/*"], + "@/widgets/*": ["./widgets/*"], + "@/pages/*": ["./pages/*"], + "@common/components": ["../../packages/common/components/dist/"], + "@common/network": ["../../packages/common/network/"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/_template/widgets/README.md b/apps/_template/widgets/README.md new file mode 100644 index 000000000..cd160abd9 --- /dev/null +++ b/apps/_template/widgets/README.md @@ -0,0 +1,135 @@ +# Widgets Layer + +**Widgets**는 큰 규모의 독립적인 UI 블록을 위한 레이어입니다. 여러 features와 entities를 조합하여 완전한 기능을 제공하는 복합 컴포넌트를 담습니다. + +## 목적 + +- 큰 규모의 독립적인 UI 블록 구성 +- 여러 features와 entities를 하나의 응집력 있는 UI로 결합 +- 여러 페이지에서 재사용되는 복잡한 UI 제공 +- 페이지 레벨 레이아웃 및 섹션 관리 + +## Widget으로 만들어야 하는 경우 + +Widget은 다음 조건에 해당할 때 사용하세요: + +✅ **Widget으로 만들기 좋은 경우**: +- 여러 페이지에서 재사용되는 큰 UI 블록 +- 페이지에 여러 개의 큰 독립적 블록이 있고, 이것이 그 중 하나인 경우 +- 페이지 레벨 레이아웃 컴포넌트 (nested routing 시스템 사용 시) + +❌ **Widget으로 만들지 말아야 하는 경우**: +- UI 블록이 페이지의 대부분을 차지하고 재사용되지 않는 경우 → 페이지 슬라이스 내부에 직접 배치 +- 작은 재사용 컴포넌트 → `shared/ui`에 배치 + +## 구조 + +각 widget 슬라이스는 다음 세그먼트로 구성됩니다: + +``` +widgets/ +└── [widget-name]/ + ├── ui/ # 위젯의 메인 UI 컴포넌트 + ├── model/ # 위젯 내부 상태 관리 (선택적) + ├── api/ # 위젯 전용 API 호출 (선택적) + ├── lib/ # 위젯 내부 유틸리티 (선택적) + └── index.ts # Public API +``` + +### 세그먼트 설명 + +- **ui**: 여러 features와 entities를 조합한 위젯 UI +- **model**: 위젯 내부 상태 및 로직 (선택적) +- **api**: 위젯이 필요로 하는 데이터 fetching (선택적) +- **lib**: 위젯 내부에서만 사용되는 헬퍼 함수 (선택적) +- **index.ts**: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export) + +## 예시 + +```typescript +// widgets/header/ui/header.tsx +import { Logo } from 'shared/ui/logo'; +import { Navigation } from 'shared/ui/navigation'; +import { UserMenu } from 'features/user-menu'; +import { NotificationBell } from 'features/notifications'; + +export function Header() { + return ( +
+ + + + +
+ ); +} + +// widgets/product-list/ui/product-list.tsx +import { useProducts } from '../api/use-products'; +import { ProductCard } from 'entities/product'; +import { AddToCartButton } from 'features/cart-add-item'; + +export function ProductList() { + const { data: products, isLoading } = useProducts(); + + if (isLoading) return
로딩 중...
; + + return ( +
+ {products.map((product) => ( +
+ + +
+ ))} +
+ ); +} + +// widgets/product-list/api/use-products.ts +import { useQuery } from '@tanstack/react-query'; +import { getProducts } from 'entities/product'; + +export function useProducts() { + return useQuery({ + queryKey: ['products'], + queryFn: getProducts, + }); +} + +// widgets/product-list/index.ts +export { ProductList } from './ui/product-list'; +``` + +## 레이어 의존성 규칙 + +✅ **허용되는 의존성**: +- `shared` 레이어의 모듈 +- `entities` 레이어의 모듈 +- `features` 레이어의 모듈 +- 같은 widget 슬라이스 내의 다른 세그먼트 + +❌ **금지된 의존성**: +- 다른 `widgets` 슬라이스 (widget 간 직접 의존 금지) +- `pages` 레이어 +- `app` 레이어 + +## 명명 규칙 + +- 소문자와 대시 사용: `header`, `sidebar`, `product-list` +- 명사 형태 권장: UI 블록의 성격을 나타내는 이름 사용 +- 구체적으로: 위젯의 역할이 명확하게 드러나도록 + +## Nested Routing 사용 시 + +Remix 같은 nested routing 시스템을 사용한다면: +- Widgets 레이어를 flat routing의 Pages 레이어처럼 사용 가능 +- 완전한 라우터 블록 생성 (데이터 fetching, loading states, error boundaries 포함) +- 페이지 레이아웃을 이 레이어에 저장 가능 + +## 주의사항 + +1. **독립성 유지**: 각 위젯은 독립적으로 작동 가능해야 합니다 +2. **적절한 크기**: 너무 작은 컴포넌트는 `shared/ui`에, 재사용되지 않으면 `pages`에 배치 +3. **응집력**: 관련된 features와 entities를 의미 있게 조합 +4. **재사용 최적화**: 여러 페이지에서 동일한 방식으로 사용될 수 있도록 설계 diff --git a/apps/as/next-env.d.ts b/apps/as/next-env.d.ts new file mode 100644 index 000000000..1b3be0840 --- /dev/null +++ b/apps/as/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/cm/.env.example b/apps/cm/.env.example index 9f786831b..5d9747b57 100644 --- a/apps/cm/.env.example +++ b/apps/cm/.env.example @@ -1,7 +1,14 @@ # apps/cs/src/shared/config/env.ts 참고 -NEXT_PUBLIC_PORT=3000 -NEXT_PUBLIC_PROXY_HOST=http://localhost:3000 -NEXT_PUBLIC_SITE_ID=CS -NEXT_PUBLIC_SUB_DOMAIN=http://localhost:3000 -NEXT_PUBLIC_SERVER_API_URL=http://192.168.0.102:8000 -NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 \ No newline at end of file +NEXT_PUBLIC_PORT=3001 +NEXT_PUBLIC_PROXY_HOST=http://localhost:3001 +NEXT_PUBLIC_SITE_ID=CM +NEXT_PUBLIC_SUB_DOMAIN=http://localhost:13000 +# NEXT_PUBLIC_SERVER_API_URL=http://localhost:8000 +NEXT_PUBLIC_SERVER_API_URL=http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_LINK_BACKEND_API_HOST=http://rsone.iptime.org:10014 +NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 + +NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083 +NEXT_PUBLIC_SERVER_API_URL3=https://apigateway.apps.rsonesoft.com +NEXT_PUBLIC_SN=https://devlion.hasuon.co.kr +NEXT_PUBLIC_WR=https://devtiger.hasuon.co.kr diff --git a/apps/cm/.env.local b/apps/cm/.env.local deleted file mode 100644 index 1ef6a3045..000000000 --- a/apps/cm/.env.local +++ /dev/null @@ -1,10 +0,0 @@ -# apps/cs/src/shared/config/env.ts 참고 -NEXT_PUBLIC_PORT=3000 -NEXT_PUBLIC_PROXY_HOST=http://localhost:3000 -NEXT_PUBLIC_SITE_ID=CS -NEXT_PUBLIC_SUB_DOMAIN=http://localhost:13000 -NEXT_PUBLIC_SERVER_API_URL=http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_LINK_BACKEND_API_HOST=http://rsone.iptime.org:10014 -NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 - -NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083/ \ No newline at end of file diff --git a/apps/cm/.env.production b/apps/cm/.env.production deleted file mode 100644 index 9f786831b..000000000 --- a/apps/cm/.env.production +++ /dev/null @@ -1,7 +0,0 @@ -# apps/cs/src/shared/config/env.ts 참고 -NEXT_PUBLIC_PORT=3000 -NEXT_PUBLIC_PROXY_HOST=http://localhost:3000 -NEXT_PUBLIC_SITE_ID=CS -NEXT_PUBLIC_SUB_DOMAIN=http://localhost:3000 -NEXT_PUBLIC_SERVER_API_URL=http://192.168.0.102:8000 -NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 \ No newline at end of file diff --git a/apps/cm/.env.zip b/apps/cm/.env.zip deleted file mode 100644 index 610032d09..000000000 Binary files a/apps/cm/.env.zip and /dev/null differ diff --git a/apps/cm/app/auth/logout/route.ts b/apps/cm/app/api/auth/logout/route.ts similarity index 100% rename from apps/cm/app/auth/logout/route.ts rename to apps/cm/app/api/auth/logout/route.ts diff --git a/apps/cm/app/api/proxy/[...url]/route.ts b/apps/cm/app/api/proxy/[...url]/route.ts index 761991e39..105b36fa7 100644 --- a/apps/cm/app/api/proxy/[...url]/route.ts +++ b/apps/cm/app/api/proxy/[...url]/route.ts @@ -36,9 +36,15 @@ async function handler( } try { - if (!isRefreshHandler) { - body = await request.json(); - console.log(`[Proxy] Request body:`, body); + // GET 요청이 아닐 때만 body 파싱 + if (method !== "GET" && !isRefreshHandler) { + try { + body = await request.json(); + console.log(`[Proxy] Request body:`, body); + } catch { + // body가 없을 수 있음 + body = {}; + } } const targetUrl = `${SERVER_API_URL}/${params.url.join("/")}`; @@ -47,7 +53,7 @@ async function handler( const result = await fetch(targetUrl, { method, headers, - body: JSON.stringify(body), + body: method !== "GET" ? JSON.stringify(body) : undefined, }); console.log(`[Proxy] Response status: ${result.status}`); @@ -55,12 +61,14 @@ async function handler( // 2FA 관련 엔드포인트인 경우 응답 본문도 확인 const is2FAEndpoint = params.url.includes("2fa") || params.url.includes("auth"); + // 배너 API는 인증 없이 호출 가능 + const isBannerEndpoint = params.url.includes("bnrs"); let responseData = {}; - if (is2FAEndpoint) { + if (is2FAEndpoint || isBannerEndpoint) { try { responseData = await result.json(); - console.log(`[Proxy] 2FA Response data:`, responseData); + console.log(`[Proxy] ${is2FAEndpoint ? '2FA' : 'Banner'} Response data:`, responseData); } catch { // JSON 파싱 실패 시 빈 객체 사용 console.log(`[Proxy] Failed to parse JSON response`); @@ -74,15 +82,15 @@ async function handler( console.log(`[Proxy] Tokens - accessToken: ${!!accessToken}, userId: ${!!userId}, refreshToken: ${!!refreshToken}`); console.log(`[Proxy] Available headers:`, Array.from(result.headers.keys())); - // 2FA 엔드포인트이고 실패한 경우 실제 오류 메시지 반환 - if (!result.ok && is2FAEndpoint) { - console.log(`[Proxy] 2FA endpoint failed, returning actual error`); + // 2FA 또는 배너 엔드포인트이고 실패한 경우 실제 오류 메시지 반환 + if (!result.ok && (is2FAEndpoint || isBannerEndpoint)) { + console.log(`[Proxy] ${is2FAEndpoint ? '2FA' : 'Banner'} endpoint failed, returning actual error`); return NextResponse.json(responseData, { status: result.status }); } - // 2FA 엔드포인트이고 성공한 경우 (토큰이 헤더에 없을 수 있음) - if (result.ok && is2FAEndpoint) { - console.log(`[Proxy] 2FA endpoint succeeded, returning response data`); + // 2FA 또는 배너 엔드포인트이고 성공한 경우 (토큰이 헤더에 없을 수 있음) + if (result.ok && (is2FAEndpoint || isBannerEndpoint)) { + console.log(`[Proxy] ${is2FAEndpoint ? '2FA' : 'Banner'} endpoint succeeded, returning response data`); return NextResponse.json(responseData, { status: result.status }); } @@ -143,3 +151,11 @@ export async function PUT( const params = await props.params; return handler(request, params, "PUT"); } + +export async function GET( + request: Request, + props: { params: Promise<{ url: string[] }> }, +) { + const params = await props.params; + return handler(request, params, "GET"); +} diff --git a/apps/cm/eslint.config.mjs b/apps/cm/eslint.config.mjs index 9109635e9..c9e490efd 100644 --- a/apps/cm/eslint.config.mjs +++ b/apps/cm/eslint.config.mjs @@ -12,6 +12,11 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), { + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname, + }, + }, rules: { "react-hooks/exhaustive-deps": "off", }, diff --git a/apps/cm/middleware.ts b/apps/cm/middleware.ts index ac1e5464b..241df1e74 100644 --- a/apps/cm/middleware.ts +++ b/apps/cm/middleware.ts @@ -17,8 +17,8 @@ const serverApiUrl = const serverApiUrl3 = process.env.NEXT_PUBLIC_SERVER_API_URL3 ?? serverApiUrl; - const serverApiUrl4 = - process.env.NEXT_PUBLIC_SERVER_API_URL4 ?? serverApiUrl; + // const serverApiUrl4 = + // process.env.NEXT_PUBLIC_SERVER_API_URL4 ?? serverApiUrl; const serverApiUrl5 = process.env.NEXT_PUBLIC_SERVER_API_URL5 ?? serverApiUrl; @@ -33,19 +33,6 @@ export function middleware(req: NextRequest) { `[Request]\n Original Pathname: ${pathname}`, ]; - if (pathname.startsWith("/server")) { - const queryParams = searchParams.toString(); - const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ - queryParams ? `?${queryParams}` : "" - }`; - - logMessages.push(` Rewritten URL: ${newUrl}`); - logMessages.push("\n--------------------------------\n"); - console.log(logMessages.join("\n")); - - return NextResponse.rewrite(new URL(newUrl)); - } - if (pathname.startsWith("/attach-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; @@ -57,7 +44,7 @@ export function middleware(req: NextRequest) { logMessages.push(` Rewritten URL: ${newUrl}`); logMessages.push("\n--------------------------------\n"); - console.log(logMessages.join("\n")); + // console.log(logMessages.join("\n")); return NextResponse.rewrite(new URL(newUrl), { headers: requestHeaders, @@ -65,11 +52,13 @@ export function middleware(req: NextRequest) { } - if (pathname.startsWith("/sewerage-service")) { + if (pathname.startsWith("/server/sewerage-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - const newUrl = `${serverApiUrl2}${pathname}`; + + // const decodedQuery = toDecodedQueryString(searchParams); + const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "/")}${searchParams ? `?${searchParams}` : ""}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -87,7 +76,7 @@ export function middleware(req: NextRequest) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - const newUrl = `${serverApiUrl3}${pathname}`; + const newUrl = `${serverApiUrl2}${pathname}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -101,11 +90,12 @@ export function middleware(req: NextRequest) { }); } - if (pathname.startsWith("/operation-service")) { + if (pathname.startsWith("/server/operation-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - const newUrl = `${serverApiUrl4}${pathname}`; + // const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}`; + const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "/")}${searchParams ? `?${searchParams}` : ""}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -119,11 +109,12 @@ export function middleware(req: NextRequest) { }); } - if (pathname.startsWith("/ic-service")) { + if (pathname.startsWith("/server/ic-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - const newUrl = `${serverApiUrl5}${pathname}`; + + const newUrl = `${serverApiUrl3}${pathname.replace("/server/", "/")}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -137,11 +128,12 @@ export function middleware(req: NextRequest) { }); } - if (pathname.startsWith("/dc-service")) { + if (pathname.startsWith("/server/dc-service")) { const requestHeaders = new Headers(req.headers); const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; - const newUrl = `${serverApiUrl5}${pathname}`; + + const newUrl = `${serverApiUrl3}${pathname.replace("/server/", "/")}${searchParams ? `?${searchParams}` : ""}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -163,11 +155,25 @@ export function middleware(req: NextRequest) { logMessages.push(` User Service Rewritten URL: ${newUrl}`); logMessages.push("\n--------------------------------\n"); - console.log(logMessages.join("\n")); + // console.log(logMessages.join("\n")); return NextResponse.rewrite(new URL(newUrl)); } + if (pathname.startsWith("/server")) { + const queryParams = searchParams.toString(); + const newUrl = `${serverApiUrl}${pathname.replace("/server", "/")}${ + queryParams ? `?${queryParams}` : "" + }`; + + logMessages.push(` Rewritten URL: ${newUrl}`); + logMessages.push("\n--------------------------------\n"); + // console.log(logMessages.join("\n")); + + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname === "/") { return NextResponse.redirect(new URL("/dashboard", req.url)); } @@ -178,5 +184,11 @@ export function middleware(req: NextRequest) { } export const config = { - matcher: ["/server/:path*", "/attach-service/:path*", "/user-service/:path*", "/"], + matcher: ["/server/:path*", + "/attach-service/:path*", + "/user-service/:path*", + "/sewerage-service/:path*", + "/reuse-service/:path*", + "/operation-service/:path*", + "/"], }; diff --git a/apps/cm/next.config.ts b/apps/cm/next.config.ts index 679528324..b388b76d9 100644 --- a/apps/cm/next.config.ts +++ b/apps/cm/next.config.ts @@ -3,10 +3,22 @@ import type { NextConfig } from "next"; const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000"; -const backendUrl = process.env.NEXT_PUBLIC_SERVER_API_URL || "http://192.168.0.102:8000"; - const nextConfig: NextConfig = { output: "standalone", + experimental: { + proxyTimeout: 600000, + }, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + logging: { + fetches: { + fullUrl: true, + }, + }, async rewrites() { return { beforeFiles: [ @@ -27,10 +39,6 @@ const nextConfig: NextConfig = { source: "/cms/:path*", destination: `${subDomain}/cms/:path*`, }, - { - source: "/user-service/:path*", - destination: `${backendUrl}/user-service/:path*`, - }, ], fallback: [], }; diff --git a/apps/cm/package.json b/apps/cm/package.json index e4fd57c6c..9d4e1da8f 100644 --- a/apps/cm/package.json +++ b/apps/cm/package.json @@ -25,7 +25,7 @@ "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tanstack/react-query": "^5.67.1", + "@tanstack/react-query": "^5.75.5", "@tanstack/react-query-devtools": "^5.75.5", "@tanstack/react-table": "^8.21.2", "@tinymce/tinymce-react": "^5.1.1", @@ -34,7 +34,7 @@ "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "lucide-react": "^0.474.0", - "next": "15.3.0", + "next": "15.3.8", "next-qrcode": "^2.5.1", "next-themes": "^0.4.6", "react-chartjs-2": "^5.3.0", @@ -47,17 +47,17 @@ "zustand": "^5.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3", + "@eslint/eslintrc": "^3.0.0", "@types/js-cookie": "^3.0.6", "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", - "eslint": "^9", - "eslint-config-next": "15.1.7", - "postcss": "^8", + "eslint": "^9.0.0", + "eslint-config-next": "15.4.0", + "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", "steiger": "^0.5.5", - "tailwindcss": "^3.4.1", - "typescript": "^5" + "tailwindcss": "^3.4.14", + "typescript": "^5.7.2" } -} +} \ No newline at end of file diff --git a/apps/cm/public/images/icon_loginID.png b/apps/cm/public/images/icon_loginID.png new file mode 100644 index 000000000..062b41e41 Binary files /dev/null and b/apps/cm/public/images/icon_loginID.png differ diff --git a/apps/cm/src/app/styles/components.css b/apps/cm/src/app/styles/components.css index 5b9742c0f..78dffb65b 100644 --- a/apps/cm/src/app/styles/components.css +++ b/apps/cm/src/app/styles/components.css @@ -73,12 +73,32 @@ padding: 0 !important; } +.card_paddingnone { + padding: 0 !important; +} + .card_nomargin { width: 100%; margin: 0 !important; padding: 0 !important; } +.card_title { + padding: 1rem; + margin-bottom: 1rem; +} + +.card_title .leftArea { + display: flex; + align-items: center; +} + +.card_title h4 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; +} + .topline { border-top: 1px solid #ccc; } @@ -127,3 +147,300 @@ border-bottom-color: #3f4042; } } + +/* 자료현황 스타일 */ +.mid_info_area { + padding: 10px 5px; + height: 220px; + position: relative; + margin-top: 0; +} + +.mi_list_swiper { + height: 100%; + width: 100%; +} + +.mi_list { + height: 100%; +} + +.ml_box { + position: relative; + width: 90%; + max-width: 90%; + height: 150px; + padding: 12px; + margin: 0 8px; + display: flex; + border-radius: 12px; + background: #f8f9fa; + border: 1px solid #e9ecef; + box-sizing: border-box; + transition: all 0.3s ease; +} + +.ml_box:hover { + background: #e3f2fd; + border-color: #2196f3; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.mb_section { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; +} + +.tit { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.tit i { + font-size: 20px; + margin-right: 10px; + color: #2196f3; +} + +.tt { + font-size: 16px; + font-weight: 600; + color: #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.info_row { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} + +.info_item { + text-align: center; + width: 100%; +} + +.info_label { + font-size: 12px; + color: #666; + margin-bottom: 4px; +} + +.info_value { + font-size: 18px; + font-weight: bold; + color: #2196f3; + line-height: 1; +} + +/* Swiper 네비게이션 스타일 */ +.mi_list_swiper .swiper-button-next, +.mi_list_swiper .swiper-button-prev { + color: #2196f3; + width: 30px; + height: 30px; + margin-top: -15px; +} + +.mi_list_swiper .swiper-button-next:after, +.mi_list_swiper .swiper-button-prev:after { + font-size: 16px; +} + +.mi_list_swiper .swiper-pagination { + bottom: 5px; +} + +.mi_list_swiper .swiper-pagination-bullet { + background: #2196f3; + opacity: 0.3; +} + +.mi_list_swiper .swiper-pagination-bullet-active { + opacity: 1; +} + +/* 특별시 스타일 */ +.ml_box.special-city { + background: linear-gradient(135deg, #ff6b6b, #ff8e8e); + border-color: #ff4757; + color: white; +} + +.ml_box.special-city .tt { + color: white; + font-weight: 700; +} + +.ml_box.special-city .info_label { + color: rgba(255, 255, 255, 0.8); +} + +.ml_box.special-city .info_value { + color: white; +} + +.ml_box.special-city:hover { + background: linear-gradient(135deg, #ff5252, #ff7979); + transform: translateY(-3px); + box-shadow: 0 6px 12px rgba(255, 107, 107, 0.3); +} + +/* 광역시 스타일 */ +.ml_box.metropolitan-city { + background: linear-gradient(135deg, #4facfe, #00f2fe); + border-color: #2196f3; + color: white; +} + +.ml_box.metropolitan-city .tt { + color: white; + font-weight: 600; +} + +.ml_box.metropolitan-city .info_label { + color: rgba(255, 255, 255, 0.8); +} + +.ml_box.metropolitan-city .info_value { + color: white; +} + +.ml_box.metropolitan-city:hover { + background: linear-gradient(135deg, #2196f3, #21cbf3); + transform: translateY(-3px); + box-shadow: 0 6px 12px rgba(33, 150, 243, 0.3); +} + +/* 광역도 스타일 */ +.ml_box.province { + background: linear-gradient(135deg, #a8edea, #fed6e3); + border-color: #00bcd4; + color: #333; +} + +.ml_box.province .tt { + color: #333; + font-weight: 600; +} + +.ml_box.province .info_label { + color: #666; +} + +.ml_box.province .info_value { + color: #00bcd4; +} + +.ml_box.province:hover { + background: linear-gradient(135deg, #00bcd4, #26c6da); + color: white; + transform: translateY(-3px); + box-shadow: 0 6px 12px rgba(0, 188, 212, 0.3); +} + +.ml_box.province:hover .tt, +.ml_box.province:hover .info_label, +.ml_box.province:hover .info_value { + color: white; +} + +/* 네이버 스타일 로그인 박스 */ +.login_info_box { + padding: 20px; + background: #fff; + border: 1px solid #e1e5e9; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.login_header { + display: flex; + align-items: center; + margin-bottom: 10px; + padding-bottom: 5px; + border-bottom: 1px solid #f0f0f0; + width: 100%; +} + +.login_avatar { + width: 40px; + height: 40px; + border-radius: 50%; + overflow: hidden; + margin-right: 12px; + border: 2px solid #e1e5e9; +} + +.login_avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.login_user_info { + flex: 1; +} + +.user_name { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 2px; +} + +.user_status { + font-size: 12px; + color: #03c75a; + font-weight: 500; +} + +.login_actions { + width: 100%; + display: flex; + gap: 8px; +} + +.login_btn { + flex: 1; + height: 40px; + background: #03c75a; + color: #fff; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.login_btn:hover { + background: #02b351; + transform: translateY(-1px); +} + +.signup_btn { + flex: 1; + height: 40px; + background: #fff; + color: #03c75a; + border: 1px solid #03c75a; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.signup_btn:hover { + background: #03c75a; + color: #fff; + transform: translateY(-1px); +} diff --git a/apps/cm/src/entities/auth/lib/auth-wrapper.tsx b/apps/cm/src/entities/auth/lib/auth-wrapper.tsx index 1aa4f8e61..5366696f7 100644 --- a/apps/cm/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/cm/src/entities/auth/lib/auth-wrapper.tsx @@ -5,16 +5,15 @@ import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; +import { isAtLoginRedirectUrl } from "@common/network"; import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { - // 로그인 페이지에서는 인증 체크를 건너뜀 - if (window.location.pathname === '/auth/login') { + if (isAtLoginRedirectUrl("/auth/login")) { return; } @@ -35,13 +34,8 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); + setUserInfo(null); + if (!isAtLoginRedirectUrl("/auth/login")) { router.push("/auth/login"); } } diff --git a/apps/cm/src/entities/auth/lib/token-manager.ts b/apps/cm/src/entities/auth/lib/token-manager.ts index 21ed00218..8fcf21984 100644 --- a/apps/cm/src/entities/auth/lib/token-manager.ts +++ b/apps/cm/src/entities/auth/lib/token-manager.ts @@ -1,4 +1,4 @@ -import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config"; +import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, REFRESH_TOKEN } from "@/shared/config"; import { getCookie, setCookie, removeCookie } from "@/shared/lib"; export class TokenManager { @@ -76,6 +76,8 @@ export class TokenManager { this.removeToken(CLAIM_NAME); this.removeToken(AUTH_USER_ID); + this.removeToken(REFRESH_TOKEN); + this.removeToken(ACCESS_TOKEN); } } diff --git a/apps/cm/src/entities/post/api/post-service.ts b/apps/cm/src/entities/post/api/post-service.ts index b7016b780..6b1f9c40a 100644 --- a/apps/cm/src/entities/post/api/post-service.ts +++ b/apps/cm/src/entities/post/api/post-service.ts @@ -11,9 +11,11 @@ import { type MyPostList, type PostParams, type MyPostParams, + type NoticePostParams, type PostFormData, type PostUpdateFormData, type PostService, + type NoticePostList, } from "../model/post-interface"; export class PostApiService implements PostService { @@ -28,11 +30,22 @@ export class PostApiService implements PostService { return response.data; } + + public async getNoticePostList( + params?: NoticePostParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.BOARD_SERVICE.POSTS}/recentPosts`, + { params }, + ); + return response.data; + } + public async getMyPostList( params?: MyPostParams, ): Promise { const response = await client.get( - `${ENDPOINT.BOARD_SERVICE.POSTS}/mypost`, + `${ENDPOINT.BOARD_SERVICE.MY_POSTS}`, { params }, ); return response.data; diff --git a/apps/cm/src/entities/post/index.ts b/apps/cm/src/entities/post/index.ts index e0241e2f4..e7482a879 100644 --- a/apps/cm/src/entities/post/index.ts +++ b/apps/cm/src/entities/post/index.ts @@ -4,11 +4,13 @@ export { postUpdateFormSchema, type Post, type MyPost, + type NoticePost, type MovePost, type DeletePost, type PostList, type PostParams, type MyPostParams, + type NoticePostParams, type PostFormData, type PostUpdateFormData, type PostService, diff --git a/apps/cm/src/entities/post/model/post-interface.ts b/apps/cm/src/entities/post/model/post-interface.ts index 1be87dadd..a8b23462f 100644 --- a/apps/cm/src/entities/post/model/post-interface.ts +++ b/apps/cm/src/entities/post/model/post-interface.ts @@ -12,6 +12,20 @@ export interface PostParams extends PageableParams { ansUseYn?: string; } +export interface NoticePostParams extends PageableParams { + keywordType?: string; + keyword?: string; + bbsId?: number | null; + delYn?: string; + frDateYmd?: string; + toDateYmd?: string; +} + +export interface NoticePost extends Post { + bbsNm: string; + bbsTypeCd: string; +} + export interface MyPostParams extends PageableParams { keywordType?: string; keyword?: string; @@ -99,6 +113,7 @@ export const postUpdateFormSchema = postFormSchema.omit({ export type PostList = PageableList; export type MyPostList = PageableList; +export type NoticePostList = PageableList; export type PostFormData = z.infer; export type PostUpdateFormData = z.infer; diff --git a/apps/cm/src/pages/auth/login/ui/login-page.tsx b/apps/cm/src/pages/auth/login/ui/login-page.tsx index fed7be4ad..297d28a8f 100644 --- a/apps/cm/src/pages/auth/login/ui/login-page.tsx +++ b/apps/cm/src/pages/auth/login/ui/login-page.tsx @@ -145,11 +145,11 @@ function LoginPage() {

- 유역하수도 통합플랫폼 + 유역하수도통합플랫폼
- 통합관리시스템 + 하수도 포탈

-

유역하수도 통합플랫폼에 오신 것을 환영합니다.

+

유역하수도통합플랫폼 하수도 포탈에 오신 것을 환영합니다.

logo diff --git a/apps/cm/src/pages/dashboard/api/banner-service.ts b/apps/cm/src/pages/dashboard/api/banner-service.ts new file mode 100644 index 000000000..29ea92b75 --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/banner-service.ts @@ -0,0 +1,54 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; + +export interface Banner { + bnrSn: number; + bnrTypeCd: string; + bnrTypeNm: string; + bnrTtl: string; + lnkgUrlAddr: string; + useYn: string; + atflCd: string; + mdfcnDt: string; + mdfr: string; + mdfrNm: string; +} + +export const getBannerList = async (siteId: string) => { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.BANNERS}/${siteId}`, + ); + return response.data; +}; + +export const getImageUrlByAtflCd = async (atflCd: string): Promise => { + try { + const response = await client.get( + `${ENDPOINT.ATTACH_SERVICE.IMAGES}/atflCd/${atflCd}`, + { + responseType: "blob", + }, + ); + const blob = response.data; + const url = URL.createObjectURL(blob); + return url; + } catch (error: unknown) { + const err = error as + | { status?: number; message?: string; response?: { status?: number } } + | Error + | undefined; + const status = err && typeof err === "object" && "status" in err + ? err.status + : err && typeof err === "object" && "response" in err && err.response + ? err.response.status + : undefined; + const message = err instanceof Error + ? err.message + : err && typeof err === "object" && "message" in err + ? String(err.message) + : String(error); + + + return ""; + } +}; diff --git a/apps/cm/src/pages/dashboard/api/dashboard-service.ts b/apps/cm/src/pages/dashboard/api/dashboard-service.ts deleted file mode 100644 index 94bf807a1..000000000 --- a/apps/cm/src/pages/dashboard/api/dashboard-service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { client } from "@/shared/api"; -import { ENDPOINT } from "@/shared/config"; -import type { Dashboard, DashboardService } from "../model/dashboard-interface"; - -export class DashboardApiService implements DashboardService { - public async getDashboard(): Promise { - const response = await client.get( - ENDPOINT.CMS_SERVICE.DASHBOARD, - ); - return response.data; - } -} - -export const DashboardApi = new DashboardApiService(); diff --git a/apps/cm/src/pages/dashboard/api/datastatus-service.ts b/apps/cm/src/pages/dashboard/api/datastatus-service.ts new file mode 100644 index 000000000..1c49f9834 --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/datastatus-service.ts @@ -0,0 +1,48 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageableList } from "@/shared/model"; + +export interface DataStatus { + ctpvgnsvycnt: number; + filecnt: number; + ctpvnm: string; +} + +export const getDataStatusList = async (): Promise> => { + const response = await client.get( + `${ENDPOINT.DC_SERVICE.DATA_STATUS}`, + ); + const data = response.data; + + console.log("getDataStatusList data:", data); + + return { + content: data as DataStatus[], + pageable: { + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + offset: 0, + pageNumber: 0, + pageSize: data.length as number, + paged: false, + unpaged: true, + }, + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + totalPages: 1, + totalElements: data.length, + number: 0, + numberOfElements: data.length, + size: data.length, + first: true, + last: true, + empty: data.length === 0, + }; +}; + diff --git a/apps/cm/src/pages/dashboard/api/fo-service.ts b/apps/cm/src/pages/dashboard/api/fo-service.ts new file mode 100644 index 000000000..9e9b3e02c --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/fo-service.ts @@ -0,0 +1,376 @@ +// apps/cm/src/pages/dashboard/api/fo-service.ts +import { client } from "@/shared/api"; +import { createStore } from "zustand/vanilla"; +import { create } from "zustand"; +import { ENDPOINT } from "@/shared/config"; +import type { OperWqltContent, OperFcltContent, ReuseStatusContent, OperationService +} from "../model/fo-interface"; +import type { mapOperFcltItem, mapOperFcltType } from "../model/fo-interface"; +import type { ReuseMapContent, mapReuseMapType } from "../model/fo-interface"; + +// 기준년도 조회 +export type YearState = { + year: string | null; +}; +export type YearStateActions = { + setYear: (year: string | null) => void; +}; +export type YearStore = YearState & YearStateActions; + +export const defaultYearInitState: YearState = { + year: null, +}; +export const createYearStore = (initState: YearState = defaultYearInitState) => + createStore()((set) => ({ + ...initState, + setYear: (year) => set(() => ({ year })), + })); +export const yearStore = createYearStore(); + +// 유역 +export type BasinState = { + selectedBasin: string; +}; +export type BasinStateActions = { + setSelectedBasin: (selectedBasin: string ) => void; +}; +export type BasinStore = BasinState & BasinStateActions; +export const defaultBasinInitState: BasinState = { + selectedBasin: "한강", +}; +export const createBasinStore = (initState: BasinState = defaultBasinInitState) => + createStore()((set) => ({ + ...initState, + setSelectedBasin: (selectedBasin) => set(() => ({ selectedBasin })), + })); +export const basinStore = createBasinStore(); + +//수계별 시설운영 개소수 및 가동률 +export type OperFcltState = { + operFcltStatus: OperFcltContent[] | null; +}; +export type OperFcltStateActions = { + setOperFcltStatus: (operFcltStatus: OperFcltContent[] | null) => void; +}; +export type OperFcltStore = OperFcltState & OperFcltStateActions; +// const initialOperFclt: OperFcltContent[]= [ +// { +// "cnt": 710, +// "fctCpc": 2911609.0, +// "rvbsn": "금강", +// "gubun": "R" +// }, +// { +// "cnt": 1032, +// "fctCpc": 7506047.0, +// "rvbsn": "낙동강", +// "gubun": "R" +// }, +// { +// "cnt": 1261, +// "fctCpc": 2061610.0, +// "rvbsn": "영산강", +// "gubun": "R" +// }, +// { +// "cnt": 983, +// "fctCpc": 1.3229809E7, +// "rvbsn": "한강", +// "gubun": "R" +// } +// ]; +export const defaultInitOperFcltState: OperFcltState = { + // operFcltStatus: initialOperFclt, + operFcltStatus: null, +}; +export const createOperFcltStore = (initState: OperFcltState = defaultInitOperFcltState) => + createStore()((set) => ({ + ...initState, + setOperFcltStatus: (operFcltStatus) => set(() => ({ operFcltStatus })), + })); +export const operFcltStore = createOperFcltStore(); + +export const initailMapOperFcltData: mapOperFcltType = { + colTitle: {cnt:"개소수",rt:"가동률"}, //헤더 영역 + data: [ + {name: "금강", cnt: 710, rt: 100,}, + {name: "한강", cnt: 983, rt: 100,}, + {name: "낙동강", cnt: 1032, rt: 100,}, + {name: "영산강", cnt: 1261, rt: 100,}, + ] +}; +type mapOperFcltState = { + mapOperFclt: mapOperFcltItem[]; + setMapOperFclt: (mapOperFclt: mapOperFcltItem[]) => void; +}; +export const useMapOperFcltStore = create((set) => ({ + mapOperFclt: initailMapOperFcltData.data, + setMapOperFclt: (newMapOperFclt) => set({ mapOperFclt: newMapOperFclt }), +})); + +export const initailMapReuseMapData: mapReuseMapType = { + colTitle: {reuseAmnt: "하수재이용량",cnt : "시설수" }, //헤더 영역 + data: [ + {name: "금강", reuseAmnt: 17099, cnt: 3181}, + {name: "한강", reuseAmnt: 17099, cnt: 3181}, + {name: "낙동강", reuseAmnt: 14745, cnt: 3100}, + {name: "영산강", reuseAmnt: 5073, cnt: 1662}, + ] +}; +type mapReuseMapState = { + mapData: ReuseMapContent[]; + setMapData: (mapData: ReuseMapContent[]) => void; +}; +export const useMapReuseMapStore = create((set) => ({ + mapData: initailMapReuseMapData.data, + setMapData: (newMapOperFclt) => set({ mapData: newMapOperFclt }), +})); + +/** + * OperFcltContent[]를 지도 팝업 데이터 형태인 mapOperFcltItem[]으로 변환합니다. + * @param data API 응답 데이터 + * @returns 유역별 개소수 및 가동률이 계산된 데이터 + */ +function transformToMapData(data: OperFcltContent[]): mapOperFcltItem[] { + const basinData = new Map(); + + // 1. 유역별로 가동/전체 용량 및 개소수 집계 + for (const item of data) { + if (!item.rvbsn) continue; + + const basin = basinData.get(item.rvbsn) || { operatingCpc: 0, totalCpc: 0, count: 0 }; + + basin.totalCpc += item.fctCpc; + if (item.gubun === 'R') { + basin.operatingCpc += item.fctCpc; + basin.count = item.cnt; // 'R' 구분 데이터의 개소수를 사용 + } + basinData.set(item.rvbsn, basin); + } + + // 2. mapOperFcltItem 형태로 변환 + const result: mapOperFcltItem[] = []; + for (const [name, { operatingCpc, totalCpc, count }] of basinData.entries()) { + const rt = totalCpc > 0 ? Math.round((operatingCpc / totalCpc) * 100) : 0; + result.push({ name, cnt: count, rt }); + } + return result; +} + +//수계별 유입ㆍ방류수질 분석 +export type OperWqltState = { + operWqltStatus: OperWqltContent[] | []; +}; +export type OperWqltStateActions = { + setOperWqltStatus: (operWqltStatus: OperWqltContent[] | []) => void; +}; +export type OperWqltStore = OperWqltState & OperWqltStateActions; +const initialWqltData: OperWqltContent[] = [ + { rvbsn: '금강', yr: '2020', sumInflBod: 97975, sumInflToc: 59900, sumInflSs: 103564, sumInflTp: 2879, sumInflTn: 27062, sumEfflBod: 100143, sumEfflToc: 57805, sumEfflSs: 100942, sumEfflTp: 2901, sumEfflTn: 26796, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '금강', yr: '2021', sumInflBod: 97943, sumInflToc: 55043, sumInflSs: 102727, sumInflTp: 2866, sumInflTn: 26237, sumEfflBod: 100143, sumEfflToc: 57805, sumEfflSs: 100942, sumEfflTp: 2901, sumEfflTn: 26796, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '금강', yr: '2022', sumInflBod: 104513, sumInflToc: 58471, sumInflSs: 96534, sumInflTp: 2960, sumInflTn: 27089, sumEfflBod: 100143, sumEfflToc: 57805, sumEfflSs: 100942, sumEfflTp: 2901, sumEfflTn: 26796, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '낙동강', yr: '2020', sumInflBod: 127321, sumInflToc: 79241, sumInflSs: 120184, sumInflTp: 4004, sumInflTn: 35373, sumEfflBod: 128919, sumEfflToc: 69837, sumEfflSs: 123866, sumEfflTp: 4773, sumEfflTn: 36740, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '낙동강', yr: '2021', sumInflBod: 127792, sumInflToc: 61919, sumInflSs: 123625, sumInflTp: 4157, sumInflTn: 35270, sumEfflBod: 128919, sumEfflToc: 69837, sumEfflSs: 123866, sumEfflTp: 4773, sumEfflTn: 36740, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '낙동강', yr: '2022', sumInflBod: 131644, sumInflToc: 68350, sumInflSs: 127789, sumInflTp: 6159, sumInflTn: 39578, sumEfflBod: 128919, sumEfflToc: 69837, sumEfflSs: 123866, sumEfflTp: 4773, sumEfflTn: 36740, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '영산강', yr: '2020', sumInflBod: 141092, sumInflToc: 97019, sumInflSs: 140610, sumInflTp: 14032, sumInflTn: 45880, sumEfflBod: 132293, sumEfflToc: 83642, sumEfflSs: 140673, sumEfflTp: 8419, sumEfflTn: 46126, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '영산강', yr: '2021', sumInflBod: 131004, sumInflToc: 74582, sumInflSs: 141129, sumInflTp: 5682, sumInflTn: 45056, sumEfflBod: 132293, sumEfflToc: 83642, sumEfflSs: 140673, sumEfflTp: 8419, sumEfflTn: 46126, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '영산강', yr: '2022', sumInflBod: 124784, sumInflToc: 79326, sumInflSs: 140281, sumInflTp: 5543, sumInflTn: 47443, sumEfflBod: 132293, sumEfflToc: 83642, sumEfflSs: 140673, sumEfflTp: 8419, sumEfflTn: 46126, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '한강', yr: '2020', sumInflBod: 146258, sumInflToc: 92931, sumInflSs: 151875, sumInflTp: 4151, sumInflTn: 36537, sumEfflBod: 152099, sumEfflToc: 84360, sumEfflSs: 153111, sumEfflTp: 8443, sumEfflTn: 35571, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '한강', yr: '2021', sumInflBod: 150983, sumInflToc: 77755, sumInflSs: 153838, sumInflTp: 4098, sumInflTn: 34946, sumEfflBod: 152099, sumEfflToc: 84360, sumEfflSs: 153111, sumEfflTp: 8443, sumEfflTn: 35571, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '한강', yr: '2022', sumInflBod: 159057, sumInflToc: 82394, sumInflSs: 153619, sumInflTp: 17080, sumInflTn: 35231, sumEfflBod: 152099, sumEfflToc: 84360, sumEfflSs: 153111, sumEfflTp: 8443, sumEfflTn: 35571, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '', yr: '2020', sumInflBod: 55280, sumInflToc: 35027, sumInflSs: 55125, sumInflTp: 1695, sumInflTn: 15442, sumEfflBod: 65532, sumEfflToc: 36455, sumEfflSs: 65256, sumEfflTp: 2830, sumEfflTn: 18291, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '', yr: '2021', sumInflBod: 66809, sumInflToc: 33841, sumInflSs: 65743, sumInflTp: 2015, sumInflTn: 17674, sumEfflBod: 65532, sumEfflToc: 36455, sumEfflSs: 65256, sumEfflTp: 2830, sumEfflTn: 18291, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, + { rvbsn: '', yr: '2022', sumInflBod: 74505, sumInflToc: 40498, sumInflSs: 74899, sumInflTp: 4780, sumInflTn: 21756, sumEfflBod: 65532, sumEfflToc: 36455, sumEfflSs: 65256, sumEfflTp: 2830, sumEfflTn: 18291, avgInflBod: 0, avgInflToc: 0, avgInflSs: 0, avgInflTp: 0, avgInflTn: 0, avgEfflBod: 0, avgEfflToc: 0, avgEfflSs: 0, avgEfflTp: 0, avgEfflTn: 0 }, +]; +export const defaultInitOperWqltState: OperWqltState = { + operWqltStatus: initialWqltData, +}; +export const createOperWqltStore = (initState: OperWqltState = defaultInitOperWqltState) => + createStore()((set) => ({ + ...initState, + setOperWqltStatus: (operWqltStatus) => set(() => ({ operWqltStatus })), + })); +export const operWqltStore = createOperWqltStore(); + +// 물재이용 현황 +export type ReuseState = { + reuseStatus: ReuseStatusContent[] | null; +}; +export type ReuseStateActions = { + setReuseStatus: (reuseStatus: ReuseStatusContent[] | null) => void; +}; +export type ReuseStore = ReuseState & ReuseStateActions; +const initialReuseStatusData: ReuseStatusContent[] = [ + { yr: '2021', sewage: 7335803, reclaimed: 254805510.3, rainwater: 14049224.5 }, + { yr: '2022', sewage: 7378907, reclaimed: 243457788.8, rainwater: 15128093.4 }, + { yr: '2023', sewage: 7537151, reclaimed: 222475997, rainwater: 13944732.5 }, + { yr: '2024', sewage: 7479136, reclaimed: 265625917.7, rainwater: 13305309.5 }, +]; +export const defaultInitReuseState: ReuseState = { + reuseStatus: initialReuseStatusData, +}; +export const createReuseStore = (initState: ReuseState = defaultInitReuseState) => + createStore()((set) => ({ + ...initState, + setReuseStatus: (reuseStatus) => set(() => ({ reuseStatus })), + })); +export const reuseStore = createReuseStore(); + +/** + * 두 객체 또는 배열의 깊은 비교를 수행합니다. + * @param obj1 비교할 첫 번째 객체 + * @param obj2 비교할 두 번째 객체 + * @returns 두 객체의 내용이 같으면 true, 그렇지 않으면 false + */ +// function deepEqual(obj1: any, obj2: any): boolean { +// if (obj1 === obj2) { +// return true; +// } + +// if (obj1 == null || typeof obj1 !== 'object' || obj2 == null || typeof obj2 !== 'object') { +// return false; +// } + +// const keys1 = Object.keys(obj1); +// const keys2 = Object.keys(obj2); + +// if (keys1.length !== keys2.length) { +// return false; +// } + +// for (const key of keys1) { +// if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { +// return false; +// } +// } + +// // 배열인 경우 순서와 내용이 모두 동일한지 확인 +// if (Array.isArray(obj1) && Array.isArray(obj2)) { +// if (obj1.length !== obj2.length) return false; +// for (let i = 0; i < obj1.length; i++) { +// if (!deepEqual(obj1[i], obj2[i])) return false; +// } +// } + +// return true; +// } + + +export class OperationApiService implements OperationService { + + // 기준년도 조회 + public async getStandardYear(): Promise { + const { year, setYear } = yearStore.getState(); + if (year) { + return year; + } + + const response = await client.get<{ + status: number; + data: string; + count: number; + }>(ENDPOINT.OPERATION_SERVICE.MAIN_YEAR); + + const newYear = response.data.data || '2024'; + setYear(newYear); + + return newYear; + } + + // 수계별 시설운영 개소수 및 가동률 + public async getDashOperCntRt(year: string): Promise { + const { operFcltStatus, setOperFcltStatus } = operFcltStore.getState(); + const { setMapOperFclt } = useMapOperFcltStore.getState(); + // 간단한 캐싱: 데이터가 이미 있으면 API 호출 생략 + if (operFcltStatus && operFcltStatus.length > 0) { + return operFcltStatus; + } + + const response = await client.get<{ + status: number; + data: OperFcltContent[]; + count: number; + }>(ENDPOINT.OPERATION_SERVICE.OPER_CNTRT, { + params: { + year, + }, + }); + + const list = (response.data.data ?? []).map((item) => ({ + ...item, + cnt: Number(item.cnt), + fctCpc: Number(item.fctCpc), + })); + + setOperFcltStatus(list); + setMapOperFclt(transformToMapData(list)); + return list; + } + + // 수계별 유입ㆍ방류수질 분석 + public async getDashOperWqlt(year: string): Promise { + + const response = await client.get<{ + status: number; + data: OperWqltContent[]; + count: number; + }>(ENDPOINT.OPERATION_SERVICE.OPER_WQLT, { + params: { + year, + }, + }); + + const list = (response.data.data ?? []).map((item) => ({ + ...item, + })); + + return list; + } + + // 물재이용 현황 + public async getReuseStatus(): Promise { + // API 호출 시에는 항상 최신 데이터를 가져오도록 캐싱 로직을 제거하거나 수정할 수 있습니다. + // 여기서는 기존 캐싱 로직을 유지하되, 초기 데이터가 아닌 경우에만 API를 호출하도록 합니다. + const { reuseStatus, setReuseStatus } = reuseStore.getState(); + if (reuseStatus && reuseStatus.length > 0 && reuseStatus !== defaultInitReuseState.reuseStatus) { + return reuseStatus; + } + + const response = await client.get<{ + status: number; + data: ReuseStatusContent[]; + count: number; + }>(ENDPOINT.OPERATION_SERVICE.REUSE_STATUS); + + const list = (response.data.data ?? []).map((item) => ({ + ...item, + reclaimed: Number(item.reclaimed), + rainwater: Number(item.rainwater), + })); + + setReuseStatus(list); + return list; + } + + // 수계별 하수재이용량, 시설수 + public async getReuseMap(year: string): Promise { + + const response = await client.get<{ + status: number; + data: ReuseMapContent[]; + count: number; + }>(ENDPOINT.OPERATION_SERVICE.REUSE_MAP, { + params: { + year, + }, + }); + + const list = (response.data.data ?? []).map((item) => ({ + ...item, + })); + + return list; + } +} + +export const OperationApi = new OperationApiService(); diff --git a/apps/cm/src/pages/dashboard/api/schedule-service.ts b/apps/cm/src/pages/dashboard/api/schedule-service.ts new file mode 100644 index 000000000..34b8c9dd2 --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/schedule-service.ts @@ -0,0 +1,66 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageableList } from "@/shared/model"; + +export interface ScheduleParams { + siteId?: string; + schdlTtl?: string; + schdlSeCd?: string; + schdlBgngYmd?: string; + schdlEndYmd?: string; + reptYn?: string; + commSchdlYn?: string; +} + +export interface Schedule { + schdlSn: number; + siteId: string; + schdlTtl: string; + schdlSeCd: string; + schdlSeNm?: string; + schdlBgngDt: string; + schdlEndDt: string; + reptYn: string; + commSchdlYn: string; + schdlCn?: string; + actualDate?: string; + atflCd?: string | null; +} + + +export const getScheduleList = async (params?: ScheduleParams): Promise> => { + const response = await client.get( + ENDPOINT.CMS_SERVICE.SCHEDULES, + { params }, + ); + const data = response.data; + + return { + content: data as Schedule[], + pageable: { + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + offset: 0, + pageNumber: 0, + pageSize: data.length as number, + paged: false, + unpaged: true, + }, + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + totalPages: 1, + totalElements: data.length, + number: 0, + numberOfElements: data.length, + size: data.length, + first: true, + last: true, + empty: data.length === 0, + }; +}; diff --git a/apps/cm/src/pages/dashboard/api/sn-service.ts b/apps/cm/src/pages/dashboard/api/sn-service.ts new file mode 100644 index 000000000..0beaaf59e --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/sn-service.ts @@ -0,0 +1,189 @@ +// apps/cm/src/pages/dashboard/api/sn-service.ts +import { client } from "@/shared/api"; +import { create } from "zustand"; +import { ENDPOINT } from "@/shared/config"; +import type { PplLengthContent, SewerageService } from "../model/sn-interface"; +import type { OldPplChartData, mapPplOldType, mapPplOldItem } from "../model/sn-interface"; + +export type SnState = { + snStatus: PplLengthContent[] | null; +}; +export type SnStateActions = { + setSnStatus: (snStatus: PplLengthContent[] | null) => void; +}; +export type SnStore = SnState & SnStateActions; + +const initialUsePplChartData: OldPplChartData = { + labels: ["금강", "한강", "낙동강", "영산강"], + datasets: [ + { + label: "기타", + data: [1966, 15480, 6957, 1937], + backgroundColor: "rgba(255, 178, 150, 0.9)" + }, + { + label: "오수관로", + data: [8074, 15879, 16337, 5671], + backgroundColor: "rgba(180, 240, 190, 0.95)" + }, + { + label: "우수관로", + data: [4838, 12893, 10492, 4281], + backgroundColor: "rgba(170, 210, 245, 0.95)" + } + ] +}; +const initialOldPplChartData: OldPplChartData = { + labels: ["금강", "한강", "낙동강", "영산강"], + datasets: [ + { + label: "20-25년", + data: [1140, 3181, 3100, 1662], + backgroundColor: "#3B82F6", + borderRadius: 4 + }, + { + label: "25-30년", + data: [1391, 3615, 1814, 1411], + backgroundColor: "#EF4444", + borderRadius: 4 + }, + { + label: "30년이상", + data: [2014, 10303, 9831, 2000], + backgroundColor: "#F87171", + borderRadius: 4 + } + ] +}; +type OldPplStoreState = { + chartData: OldPplChartData; + setChartData: (chartData: OldPplChartData) => void; +}; +export const useOldPplStore = create((set) => ({ + chartData: initialOldPplChartData, + setChartData: (chartData) => set(() => ({ chartData })), +})); +export const useUsePplStore = create((set) => ({ + chartData: initialUsePplChartData, + setChartData: (chartData) => set(() => ({ chartData })), +})); + +export const initailMapOldPplData: mapPplOldType = { + colTitle: { totalLen: "총길이", pplLen2025: "20~25년", pplLen2530: "25~30년", pplLen30: "30년 이상" }, //헤더 영역 + data: [ + { name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30: 2014 }, + { name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30: 10303 }, + { name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30: 9831 }, + { name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30: 2000 }, + ] +}; +type mapPplOldState = { + mapData: mapPplOldItem[]; + setMapData: (mapData: mapPplOldItem[]) => void; +}; +export const useMapOldPplStore = create((set) => ({ + mapData: initailMapOldPplData.data, + setMapData: (newMapData) => set({ mapData: newMapData }), +})); + +/** + * OldPplChartData를 지도 팝업 데이터 형태인 mapPplOldItem[]으로 변환합니다. + * @param chartData 차트 데이터 + * @returns 유역별 노후관로 현황 데이터 + */ +export function transformChartDataToMapData(chartData: OldPplChartData): mapPplOldItem[] { + const mapData: mapPplOldItem[] = []; + const { labels, datasets } = chartData; + + const pplLen2025Data = datasets.find(d => d.label === '20-25년')?.data || []; + const pplLen2530Data = datasets.find(d => d.label === '25-30년')?.data || []; + const pplLen30Data = datasets.find(d => d.label === '30년이상')?.data || []; + + labels.forEach((label, index) => { + const pplLen2025 = pplLen2025Data[index] || 0; + const pplLen2530 = pplLen2530Data[index] || 0; + const pplLen30 = pplLen30Data[index] || 0; + const totalLen = pplLen2025 + pplLen2530 + pplLen30; + + mapData.push({ + name: label, + totalLen, + pplLen2025, + pplLen2530, + pplLen30, + }); + }); + + return mapData; +} + + + +export class SewerageApiService implements SewerageService { + // 기간별 관로 총길이 + public async getPplLength(instlYmdFrom: string, instlYmdTo: string): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.PPL_LENGTH, { + params: { + instlYmdFrom, + instlYmdTo, + }, + }); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + + return list; + } + + // 기간별 용도별 관로 총길이 : useCd - SBA004 오수관, SBA003 우수관 + public async getPplUseLength(instlYmdFrom: string, instlYmdTo: string, useCd: string): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.PPL_USE_LENGTH, { + params: { + instlYmdFrom, + instlYmdTo, + useCd, + }, + }); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + return list; + } + + // 유역별 용도별 관로 길이(km) MV조회 : usgCd - SBA004 오수관, SBA003 우수관 + public async getMvUsgPplLength(): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.MV_USG_PPL_LENGTH); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + return list; + } + // 유역별 노후도별 관로 길이(km) MV조회 : yearGroup - 20년~25년미만,25년~30년미만,30년이상 + public async getMvOldPplLength(): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.MV_OLD_PPL_LENGTH); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + return list; + } + +} + +export const SewerageApi = new SewerageApiService(); diff --git a/apps/cm/src/pages/dashboard/api/support-service.ts b/apps/cm/src/pages/dashboard/api/support-service.ts new file mode 100644 index 000000000..3a4fa0bc5 --- /dev/null +++ b/apps/cm/src/pages/dashboard/api/support-service.ts @@ -0,0 +1,53 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageableList } from "@/shared/model"; + +export interface Support { + total: number; + ongoing: number; + han: number; + youngsang: number; + geum: number; + nakdong: number; + completed: number; + type: string; +} + +export const getSupportList = async (): Promise> => { + const response = await client.get( + `${ENDPOINT.IC_SERVICE.GROUPING_STATISTICS}`, + ); + const data = response.data; + + console.log("getSupportList data:", data); + + return { + content: data as Support[], + pageable: { + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + offset: 0, + pageNumber: 0, + pageSize: data.length as number, + paged: false, + unpaged: true, + }, + sort: { + sorted: false, + unsorted: true, + empty: true, + }, + totalPages: 1, + totalElements: data.length, + number: 0, + numberOfElements: data.length, + size: data.length, + first: true, + last: true, + empty: data.length === 0, + }; +}; + diff --git a/apps/cm/src/pages/dashboard/lib/KoreaBasinMap.tsx b/apps/cm/src/pages/dashboard/lib/KoreaBasinMap.tsx deleted file mode 100644 index 139f9f872..000000000 --- a/apps/cm/src/pages/dashboard/lib/KoreaBasinMap.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import styles from "./KoreaBasinMap.module.css"; // 추가 - -const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요 - -const MANUAL_LABEL_POS = { - han: { x: 0.52, y: 0.14 }, - geum: { x: 0.28, y: 0.47 }, - nakdong: { x: 0.64, y: 0.47 }, - yeongsan: { x: 0.18, y: 0.77 }, -}; - -const BASIN_DATA = { - han: { - name: "한강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - geum: { - name: "금강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - nakdong: { - name: "낙동강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - yeongsan: { - name: "영산강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, -}; - -const BASIN_IDS = [ - { key: "han", labels: ["한강", "han"] }, - { key: "geum", labels: ["금강", "geum"] }, - { key: "nakdong", labels: ["낙동강", "nakdong"] }, - { key: "yeongsan", labels: ["영산강", "yeongsan"] }, -]; - -function fmt(n: number) { - return new Intl.NumberFormat("ko-KR").format(n); -} - -export function KoreaBasinMap() { - const stageRef = useRef(null); - const svgLayerRef = useRef(null); - const labelsLayerRef = useRef(null); - const popupRef = useRef(null); - const badgeRef = useRef(null); - const rowsRef = useRef(null); - const colTitleRef = useRef(null); - - useEffect(() => { - let svg: SVGSVGElement | null = null; - let gs: SVGGElement[] = []; - let labels: HTMLDivElement[] = []; - - console.log("Loading SVG from:", SVG_URL); - fetch(SVG_URL) - .then((r) => { - console.log("SVG fetch response:", r.status, r.statusText); - if (!r.ok) throw new Error(`HTTP error! status: ${r.status}`); - return r.text(); - }) - .then((txt) => { - console.log("SVG content loaded, length:", txt.length); - if (!svgLayerRef.current) { - console.error("svgLayerRef.current is null"); - return; - } - svgLayerRef.current.innerHTML = txt; - svg = svgLayerRef.current.querySelector("svg"); - if (!svg) { - console.error("SVG element not found in loaded content"); - return; - } - console.log("SVG element found:", svg); - - // viewBox/크기 세팅 - if (!svg.hasAttribute("viewBox")) { - const bb = svg.getBBox(); - svg.setAttribute( - "viewBox", - [bb.x, bb.y, bb.width, bb.height].join(" "), - ); - } - const vb = (svg.getAttribute("viewBox") || "").split(/\s+/).map(Number); - if (vb.length === 4 && vb[3]! > 0 && stageRef.current) { - stageRef.current!.style.aspectRatio = String(vb[2]! / vb[3]!); - } - svg!.setAttribute("preserveAspectRatio", "xMidYMid meet"); - svg.style.width = "100%"; - svg.style.height = "100%"; - - // 1) 지도 그룹 수집 - gs = []; - console.log("Looking for basin groups in SVG..."); - BASIN_IDS.forEach((item) => { - let g: SVGGElement | null = null; - for (const id of item.labels) { - g = svg!.getElementById(id) as SVGGElement | null; - if (g) { - console.log(`Found group for ${item.key} with id '${id}':`, g); - break; - } - } - if (!g) { - console.warn(`No group found for ${item.key} with labels:`, item.labels); - return; - } - g.classList.add("basin"); - (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key = item.key; - (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.label = item.labels[0] || ''; - - // 패스에 기본 스타일 적용 - const paths = g.querySelectorAll('path'); - paths.forEach((path) => { - path.style.fill = 'rgba(0, 150, 255, 0.2)'; - path.style.stroke = '#0066cc'; - path.style.strokeWidth = '1'; - path.style.vectorEffect = 'non-scaling-stroke'; - }); - - gs.push(g); - }); - console.log(`Found ${gs.length} basin groups`); - - // 각 유역 그룹의 패스 정보 확인 - gs.forEach((g, index) => { - const paths = g.querySelectorAll('path'); - console.log(`Group ${index} (${(g as SVGGElement & { dataset: { key: string } }).dataset.key}):`, { - element: g, - pathCount: paths.length, - paths: Array.from(paths).map(p => ({ - className: p.className.baseVal, - d: p.getAttribute('d')?.substring(0, 50) + '...' - })) - }); - }); - - // 2) 함수들 먼저 정의 - const clear = () => { - gs.forEach((el) => el.classList.remove("active")); - if (popupRef.current) { - if (styles.show) { - popupRef.current.classList.remove(styles.show); - } - popupRef.current.style.opacity = "0"; - popupRef.current.style.visibility = "hidden"; - } - if (badgeRef.current) { - if (styles.show) { - badgeRef.current.classList.remove(styles.show); - } - badgeRef.current.style.opacity = "0"; - badgeRef.current.style.visibility = "hidden"; - } - labels.forEach((l) => l.classList.remove("active")); - }; - - function showFor(g: SVGGElement) { - console.log("showFor called for:", g); - const key = (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key; - const data = (BASIN_DATA as Record)[key] || { - name: (g as SVGGElement & { dataset: { label: string } }).dataset.label, - colTitle: "", - rows: [], - }; - console.log("Data for popup:", data); - - if (badgeRef.current) badgeRef.current.textContent = data.name; - if (colTitleRef.current) - colTitleRef.current.textContent = data.colTitle || "???"; - if (rowsRef.current) - rowsRef.current.innerHTML = data.rows - .map( - (r: (string | number)[]) => - `${r[0]}${fmt(Number(r[1]) || 0)}`, - ) - .join(""); - - if (!stageRef.current) return; - const sRect = stageRef.current.getBoundingClientRect(), - r = g.getBoundingClientRect(); - const cx = (r.left + r.right) / 2 - sRect.left; - const ty = r.top - sRect.top; - - console.log("Popup position:", { cx, ty }); - - if (badgeRef.current) { - badgeRef.current.style.left = cx + "px"; - badgeRef.current.style.top = ty + 24 + "px"; - } - if (popupRef.current) { - popupRef.current.style.left = cx + "px"; - popupRef.current.style.top = ty + 6 + "px"; - popupRef.current.style.opacity = "1"; - popupRef.current.style.visibility = "visible"; - if (styles.show) { - popupRef.current.classList.add(styles.show); - } - console.log("Added show class to popup:", popupRef.current.classList.toString()); - } - if (badgeRef.current && styles.show) { - badgeRef.current.classList.add(styles.show); - } - - if (popupRef.current) { - const pRect = popupRef.current.getBoundingClientRect(); - const x = Math.max( - 12 + pRect.width / 2, - Math.min(cx, sRect.width - 12 - pRect.width / 2), - ); - popupRef.current.style.left = x + "px"; - if (badgeRef.current) badgeRef.current.style.left = x + "px"; - } - } - - function showForWithLabel(g: SVGGElement) { - console.log("showForWithLabel called for:", g); - showFor(g); - const idx = gs.indexOf(g); - labels.forEach((l) => l.classList.remove("active")); - if (idx >= 0) labels[idx]!.classList.add("active"); - } - - // 3) 라벨 DOM 생성 - labels = []; - if (labelsLayerRef.current) { - labelsLayerRef.current.innerHTML = ""; - gs.forEach((g) => { - const el = document.createElement("div"); - el.className = styles.basinLabel || ''; - el.textContent = (g as SVGGElement & { dataset: { label: string } }).dataset.label || ''; - labelsLayerRef.current!.appendChild(el); - labels.push(el); - }); - } - - // 3) 라벨 위치 - function positionLabels() { - if (!stageRef.current || !svg) return; - const sRect = stageRef.current.getBoundingClientRect(); - const inset = - parseFloat( - getComputedStyle(stageRef.current).getPropertyValue( - "--map-inset", - ) || "0", - ) / 100; - const left = sRect.width * inset, - top = sRect.height * inset; - const w = sRect.width * (1 - inset * 2), - h = sRect.height * (1 - inset * 2); - const [vx, vy, vw, vh] = ( - svg!.getAttribute("viewBox") || "0 0 100 100" - ) - .split(/\s+/) - .map(Number); - - const svgRect = svg.getBoundingClientRect(); - const stageRect = stageRef.current.getBoundingClientRect(); - const offsetX = svgRect.left - stageRect.left - left; - const offsetY = svgRect.top - stageRect.top - top; - - gs.forEach((g, i) => { - const key = (g as SVGGElement & { dataset: { key: string } }).dataset.key; - const manual = (MANUAL_LABEL_POS as Record)[key]; - let px, py; - if ( - manual && - (("x" in manual && "y" in manual) || - ("px" in manual && "py" in manual)) - ) { - if ("px" in manual) { - px = left + (manual.px || 0) + offsetX; - py = top + (manual.py || 0) + offsetY; - } else { - px = left + w * (manual.x || 0) + offsetX; - py = top + h * (manual.y || 0) + offsetY; - } - } else { - const b = g.getBBox(); - const cx = b.x + b.width / 2, - cy = b.y + b.height / 2; - px = ((cx - vx!) / vw!) * w + left + offsetX; - py = ((cy - vy!) / vh!) * h + top + offsetY; - } - labels[i]!.style.left = `${px}px`; - labels[i]!.style.top = `${py + 18}px`; - }); - } - - // 4) 상호작용 (함수들은 이미 위에서 정의됨) - gs.forEach((g) => { - g.addEventListener("mouseenter", () => { - clear(); - g.classList.add("active"); - showForWithLabel(g); - }); - g.addEventListener("mousemove", () => { - if (g.classList.contains("active")) showForWithLabel(g); - }); - g.addEventListener("mouseleave", clear); - g.addEventListener("click", (e) => { - e.stopPropagation(); - clear(); - g.classList.add("active"); - showForWithLabel(g); - }); - }); - if (stageRef.current) { - stageRef.current.addEventListener("mouseleave", clear); - stageRef.current.addEventListener("click", () => clear()); - } - - // 5) 실행 - positionLabels(); - window.addEventListener("resize", positionLabels); - new window.ResizeObserver(positionLabels).observe(stageRef.current!); - }) - .catch((error) => { - console.error("Error loading SVG:", error); - if (svgLayerRef.current) - svgLayerRef.current.innerHTML = - `
지도를 불러올 수 없습니다. (${error.message})
`; - }); - - // cleanup - return () => { - window.removeEventListener("resize", () => {}); - }; - }, []); - - // 스타일은 필요에 따라 tailwind, css 등으로 커스터마이즈하세요. - return ( -
-
-
-
-
- -
- ); -} diff --git a/apps/cm/src/pages/dashboard/lib/KoreaBasinMap.module.css b/apps/cm/src/pages/dashboard/lib/korea-basin-map.module.css similarity index 100% rename from apps/cm/src/pages/dashboard/lib/KoreaBasinMap.module.css rename to apps/cm/src/pages/dashboard/lib/korea-basin-map.module.css diff --git a/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx b/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx new file mode 100644 index 000000000..c4eedbd10 --- /dev/null +++ b/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx @@ -0,0 +1,502 @@ +"use client"; +/** + * 유역 지도 + */ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useStore } from "zustand"; +import styles from "./korea-basin-map.module.css"; // 추가 +import { basinStore } from "../api/fo-service"; +import { basinKeyMap,MANUAL_LABEL_POS } from "../model/map-data"; +import { BASIN_DATA, BASIN_IDS } from "../model/map-data"; +import { svcInfo } from "../model/map-data"; +import type { mapOperFcltType, mapReuseMapType} from "../model/fo-interface"; +import type { mapPplOldType } from "../model/sn-interface"; +import { useMapOperFcltStore, OperationApi, useMapReuseMapStore } from "../api/fo-service"; +import { useMapOldPplStore } from "../api/sn-service"; +import {Button} from "@common/components"; + +const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요 + +function fmt(n: number) { + return new Intl.NumberFormat("ko-KR").format(n); +} + +const mapOperFcltData: mapOperFcltType = { + colTitle: {cnt:"개소수",rt:"가동률"}, //헤더 영역 + data: [ + {name: "금강", cnt: 710, rt: 100,}, + {name: "한강", cnt: 983, rt: 100,}, + {name: "낙동강", cnt: 1032, rt: 100,}, + {name: "영산강", cnt: 1261, rt: 100,}, + ] +}; +const mapPplOldData: mapPplOldType = { + colTitle: {totalLen: "총길이",pplLen2025: "20~25년",pplLen2530: "25~30년",pplLen30 : "30년 이상" }, //헤더 영역 + data: [ + {name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30 : 2014}, + {name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30 : 10303}, + {name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30 : 9831}, + {name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30 : 2000}, + ] +}; +const mapReuseMapData: mapReuseMapType = { + colTitle: {reuseAmnt: "하수재이용량",cnt : "시설수" }, //헤더 영역 + data: [ + {name: "금강", reuseAmnt: 17099, cnt: 3181}, + {name: "한강", reuseAmnt: 17099, cnt: 3181}, + {name: "낙동강", reuseAmnt: 14745, cnt: 3100}, + {name: "영산강", reuseAmnt: 5073, cnt: 1662}, + ] +}; + +export function KoreaBasinMap() { + const stageRef = useRef(null); + const svgLayerRef = useRef(null); + const labelsLayerRef = useRef(null); + const popupRef = useRef(null); + const [services, setServices] = useState(svcInfo); + const [popupContent, setPopupContent] = useState<{ title: string; items: { header: string; data: string }[] }>({ + title: '', + items: [], + }); + const { selectedBasin, setSelectedBasin } = useStore(basinStore); + const selectedBasinKey = basinKeyMap[selectedBasin]; + const {mapOperFclt} = useMapOperFcltStore(); + const {mapData: mapPplOld} = useMapOldPplStore(); + const {setMapData: setMapReuseMapData} = useMapReuseMapStore(); + mapOperFcltData.data = mapOperFclt; + mapPplOldData.data = mapPplOld; + // mapReuseMapData.data = mapReuseMap; + let standardYear = '2024'; + useEffect(() => { + const fetchData = async () => { + try { + standardYear = await OperationApi.getStandardYear(); + const mapReuseMapRslt = await OperationApi.getReuseMap(standardYear); + setMapReuseMapData(mapReuseMapRslt); + mapReuseMapData.data = mapReuseMapRslt; + } catch (e) { + console.error("하수재이용량 map 데이터 조회 실패", e); + } + }; + fetchData(); + }, []); + // 유역정보 + const showFor = useCallback((g: SVGGElement) => { + const activeService = services.find(s => s.active); + if (!activeService) return; + + const key = (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key; + const basinName = BASIN_DATA[key as keyof typeof BASIN_DATA]?.name || (g as SVGGElement & { dataset: { label: string } }).dataset.label; + + const newPopupContent: { title: string; items: { header: string; data: string }[] } = { title: '', items: [] }; + + if (activeService.svcId === 'fo') { // 운영실태 + const item = mapOperFcltData.data.find(d => d.name === basinName); + newPopupContent.title = `${standardYear}년도 ${basinName}유역 시설운영 현황`; + newPopupContent.items.push({ + header: mapOperFcltData.colTitle.cnt, + data: item ? `${fmt(item.cnt)}개소` : '-', + }); + newPopupContent.items.push({ + header: mapOperFcltData.colTitle.rt, + data: item ? `${item.rt}%` : '-', + }); + } else if (activeService.svcId === 'sn') { // 하수관로 + const item = mapPplOldData.data.find(d => d.name === basinName); + newPopupContent.title = `${standardYear}년도 ${basinName}유역 노후하수관로 현황`; + const colTitles = mapPplOldData.colTitle; + newPopupContent.items.push({ + header: colTitles.totalLen, + data: item ? `${fmt(item.totalLen)}km` : '-', + }); + newPopupContent.items.push({ + header: colTitles.pplLen2025, + data: item ? `${fmt(item.pplLen2025)}km` : '-', + }); + newPopupContent.items.push({ + header: colTitles.pplLen2530, + data: item ? `${fmt(item.pplLen2530)}km` : '-', + }); + newPopupContent.items.push({ + header: colTitles.pplLen30, + data: item ? `${fmt(item.pplLen30)}km` : '-', + }); + } else if (activeService.svcId === 'wr') { // 물재이용 + const item = mapReuseMapData.data.find(d => d.name === basinName); + newPopupContent.title = `${standardYear}년도 ${basinName}유역 물재이용 현황`; + newPopupContent.items.push({ + header: mapReuseMapData.colTitle.reuseAmnt, + data: item ? `${fmt(item.reuseAmnt)}m³` : '-', + }); + newPopupContent.items.push({ + header: mapReuseMapData.colTitle.cnt, + data: item ? `${fmt(item.cnt)}개소` : '-', + }); + } + + setPopupContent(newPopupContent); + + if (!stageRef.current) return; + const sRect = stageRef.current.getBoundingClientRect(), + r = g.getBoundingClientRect(); + const cx = (r.left + r.right) / 2 - sRect.left; + const ty = r.top - sRect.top; + if (popupRef.current) { + popupRef.current.style.left = cx + "px"; + popupRef.current.style.top = ty + 6 + "px"; + popupRef.current.style.opacity = "1"; + popupRef.current.style.visibility = "visible"; + if (styles.show) { + popupRef.current.classList.add(styles.show); + } + } + + if (popupRef.current) { + const pRect = popupRef.current.getBoundingClientRect(); + const x = Math.max( + 12 + pRect.width / 2, + Math.min(cx, sRect.width - 12 - pRect.width / 2), + ); + popupRef.current.style.left = x + "px"; + } + }, [services, mapOperFclt]); + + // selectedBasinKey prop이 변경될 때 지도를 업데이트하는 useEffect + useEffect(() => { + if (selectedBasinKey) { + const svg = svgLayerRef.current?.querySelector("svg"); + if (!svg) return; + + // 모든 유역의 활성화 상태를 초기화합니다. + svg.querySelectorAll('.basin.active').forEach(el => el.classList.remove('active')); + + const targetGroup = svg.querySelector(`.basin[data-key="${selectedBasinKey}"]`) as SVGGElement | null; + targetGroup?.classList.add('active'); + } + }, [selectedBasinKey]); + + useEffect(() => { + let svg: SVGSVGElement | null = null; + let gs: SVGGElement[] = []; + let labels: HTMLDivElement[] = []; + + const clear = () => { + gs.forEach((el) => el.classList.remove("active")); + if (popupRef.current) { + if (styles.show) { + popupRef.current.classList.remove(styles.show); + } + popupRef.current.style.opacity = "0"; + } + labels.forEach((l) => l.classList.remove("active")); + }; + + // const positionLabels = () => { + // // This function's content is moved inside the fetch callback + // }; + + // console.log("Loading SVG from:", SVG_URL); + fetch(SVG_URL) + .then((r) => { + // console.log("SVG fetch response:", r.status, r.statusText); + if (!r.ok) throw new Error(`HTTP error! status: ${r.status}`); + return r.text(); + }) + .then((txt) => { + // console.log("SVG content loaded, length:", txt.length); + if (!svgLayerRef.current) { + console.error("svgLayerRef.current is null"); + return; + } + svgLayerRef.current.innerHTML = txt; + svg = svgLayerRef.current.querySelector("svg"); + if (!svg) { + console.error("SVG element not found in loaded content"); + return; + } + // console.log("SVG element found:", svg); + + // viewBox/크기 세팅 + if (!svg.hasAttribute("viewBox")) { + const bb = svg.getBBox(); + svg.setAttribute( + "viewBox", + [bb.x, bb.y, bb.width, bb.height].join(" "), + ); + } + const vb = (svg.getAttribute("viewBox") || "").split(/\s+/).map(Number); + if (vb.length === 4 && vb[3]! > 0 && stageRef.current) { + stageRef.current!.style.aspectRatio = String(vb[2]! / vb[3]!); + } + svg!.setAttribute("preserveAspectRatio", "xMidYMid meet"); + svg.style.width = "100%"; + svg.style.height = "100%"; + + // 1) 지도 그룹 수집 + gs = []; + // console.log("Looking for basin groups in SVG..."); + BASIN_IDS.forEach((item) => { + let g: SVGGElement | null = null; + for (const id of item.labels) { + g = svg!.getElementById(id) as SVGGElement | null; + if (g) { + // console.log(`Found group for ${item.key} with id '${id}':`, g); + break; + } + } + if (!g) { + console.warn(`No group found for ${item.key} with labels:`, item.labels); + return; + } + g.classList.add("basin"); + (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key = item.key; + (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.label = item.labels[0] || ''; + + // 패스에 기본 스타일 적용 + const paths = g.querySelectorAll('path'); + paths.forEach((path) => { + path.style.fill = 'rgba(0, 150, 255, 0.2)'; + path.style.stroke = '#0066cc'; + path.style.strokeWidth = '1'; + path.style.vectorEffect = 'non-scaling-stroke'; + }); + + gs.push(g); + }); + // console.log(`Found ${gs.length} basin groups`); + + // 각 유역 그룹의 패스 정보 확인 + // gs.forEach((g, index) => { + // const paths = g.querySelectorAll('path'); + // // console.log(`Group ${index} (${(g as SVGGElement & { dataset: { key: string } }).dataset.key}):`, { + // // element: g, + // // pathCount: paths.length, + // // paths: Array.from(paths).map(p => ({ + // // className: p.className.baseVal, + // // d: p.getAttribute('d')?.substring(0, 50) + '...' + // // })) + // // }); + // }); + + function showForWithLabel(g: SVGGElement) { + // console.log("showForWithLabel called for:", g); + showFor(g); + const idx = gs.indexOf(g); + labels.forEach((l) => l.classList.remove("active")); + if (idx >= 0) labels[idx]!.classList.add("active"); + } + + // 3) 라벨 DOM 생성 + labels = []; + if (labelsLayerRef.current) { + labelsLayerRef.current.innerHTML = ""; + gs.forEach((g) => { + const el = document.createElement("div"); + el.className = styles.basinLabel || ''; + el.textContent = (g as SVGGElement & { dataset: { label: string } }).dataset.label || ''; + labelsLayerRef.current!.appendChild(el); + labels.push(el); + }); + } + + // 3) 라벨 위치 + const positionLabels = () => { + if (!stageRef.current || !svg) return; + const sRect = stageRef.current.getBoundingClientRect(); + const inset = + parseFloat( + getComputedStyle(stageRef.current).getPropertyValue( + "--map-inset", + ) || "0", + ) / 100; + const left = sRect.width * inset, + top = sRect.height * inset; + const w = sRect.width * (1 - inset * 2), + h = sRect.height * (1 - inset * 2); + const [vx, vy, vw, vh] = ( + svg!.getAttribute("viewBox") || "0 0 100 100" + ) + .split(/\s+/) + .map(Number); + + const svgRect = svg.getBoundingClientRect(); + const stageRect = stageRef.current.getBoundingClientRect(); + const offsetX = svgRect.left - stageRect.left - left; + const offsetY = svgRect.top - stageRect.top - top; + + gs.forEach((g, i) => { + const key = (g as SVGGElement & { dataset: { key: string } }).dataset.key; + const manual = (MANUAL_LABEL_POS as Record)[key]; + let px, py; + if ( + manual && + (("x" in manual && "y" in manual) || + ("px" in manual && "py" in manual)) + ) { + if ("px" in manual) { + px = left + (manual.px || 0) + offsetX; + py = top + (manual.py || 0) + offsetY; + } else { + px = left + w * (manual.x || 0) + offsetX; + py = top + h * (manual.y || 0) + offsetY; + } + } else { + const b = g.getBBox(); + const cx = b.x + b.width / 2, + cy = b.y + b.height / 2; + px = ((cx - vx!) / vw!) * w + left + offsetX; + py = ((cy - vy!) / vh!) * h + top + offsetY; + } + labels[i]!.style.left = `${px}px`; + labels[i]!.style.top = `${py + 18}px`; + }); + } + + // 4) 상호작용 (함수들은 이미 위에서 정의됨) + gs.forEach((g) => { + g.addEventListener("mouseenter", () => { + clear(); + g.classList.add("active"); + showForWithLabel(g); + }); + g.addEventListener("mousemove", () => { + if (g.classList.contains("active")) showForWithLabel(g); + }); + g.addEventListener("mouseleave", clear); + g.addEventListener("click", (e) => { + e.stopPropagation(); + clear(); + g.classList.add("active"); + showForWithLabel(g); + const basinKey = g.dataset.key as keyof typeof BASIN_DATA | undefined; + if (basinKey) { + const basinName = BASIN_DATA[basinKey].name; + setSelectedBasin(basinName); + } + }); + + }); + if (stageRef.current) { + stageRef.current.addEventListener("mouseleave", clear); + stageRef.current.addEventListener("click", () => clear()); + } + + // 5) 실행 + positionLabels(); + window.addEventListener("resize", positionLabels); + new window.ResizeObserver(positionLabels).observe(stageRef.current!); + }) + .catch((error) => { + console.error("Error loading SVG:", error); + if (svgLayerRef.current) + svgLayerRef.current.innerHTML = + `
지도를 불러올 수 없습니다. (${error.message})
`; + }); + + // cleanup + return () => { + window.removeEventListener("resize", () => {}); + }; + }, [selectedBasinKey, showFor]); // 의존성 배열에 mapOperFclt 추가 불필요 + + const handleSvcInfoClick = (clickedSvcId: string) => { + setServices(prevServices => + prevServices.map(svc => ({ + ...svc, + active: svc.svcId === clickedSvcId, + })) + ); + }; + // 스타일은 필요에 따라 tailwind, css 등으로 커스터마이즈하세요. + return ( +
+
+
+

유역별 정보

+ +
+ {services.map((svc) => ( + + ))} +
+ +
+
+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/apps/cm/src/pages/dashboard/model/dashboard-interface.ts b/apps/cm/src/pages/dashboard/model/dashboard-interface.ts deleted file mode 100644 index 9e038eef7..000000000 --- a/apps/cm/src/pages/dashboard/model/dashboard-interface.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface SiteStatisticsContent { - siteId: string; - siteNm: string; - accessHour: string; - accessCnt: number; -} - -export interface MenuStatisticsContent { - menuCd: number; - menuNm: string; - accessCnt: number; -} - -export interface Dashboard { - memberTotCnt: number; - memberTodayCnt: number; - pstTotCnt: number; - pstTodayCnt: number; - accessTodayCnt: number; - accessBefDayPer: number; - apiTodayCnt: number; - waitMemberCnt: number; - waitCntnCnt: number; - notiList: []; - schdlList: []; - siteAccessList: SiteStatisticsContent[]; - menuAccessList: MenuStatisticsContent[]; -} - -export interface DashboardService { - getDashboard(): Promise; -} diff --git a/apps/cm/src/pages/dashboard/model/fo-interface.ts b/apps/cm/src/pages/dashboard/model/fo-interface.ts new file mode 100644 index 000000000..0d8a918c1 --- /dev/null +++ b/apps/cm/src/pages/dashboard/model/fo-interface.ts @@ -0,0 +1,77 @@ +export interface OperFcltContent { + rvbsn: string; // 유역 + cnt: number; // 개소수 + fctCpc: number; // 가동률 (용량) + gubun: string; // 구분 +} + +export interface OperWqltContent { + rvbsn: string; // 유역명 + yr: string; // 연도 + + sumInflBod: number; // 유입수 BOD 합계 + sumInflToc: number; // 유입수 TOC 합계 + sumInflSs: number; // 유입수 SS 합계 + sumInflTp: number; // 유입수 TP 합계 + sumInflTn: number; // 유입수 TN 합계 + + sumEfflBod: number; // 유출수 BOD 합계 + sumEfflToc: number; // 유출수 TOC 합계 + sumEfflSs: number; // 유출수 SS 합계 + sumEfflTp: number; // 유출수 TP 합계 + sumEfflTn: number; // 유출수 TN 합계 + + avgInflBod: number; // 기간 평균 유입수 BOD + avgInflToc: number; // 기간 평균 유입수 TOC + avgInflSs: number; // 기간 평균 유입수 SS + avgInflTp: number; // 기간 평균 유입수 TP + avgInflTn: number; // 기간 평균 유입수 TN + + avgEfflBod: number; // 기간 평균 유출수 BOD + avgEfflToc: number; // 기간 평균 유출수 TOC + avgEfflSs: number; // 기간 평균 유출수 SS + avgEfflTp: number; // 기간 평균 유출수 TP + avgEfflTn: number; // 기간 평균 유출수 TN +} + +export interface ReuseMapContent { + name: string; // 유역명 + reuseAmnt: number; // 하수재이용량 + cnt: number; // 시설수 +} + +export interface ReuseStatusContent { + yr: string; + sewage: number; + reclaimed: number; + rainwater: number; +} + +export interface OperationService { + getStandardYear(): Promise ; + getDashOperCntRt(year: string): Promise; + getDashOperWqlt(year: string): Promise; + getReuseStatus(): Promise; +} + +export type mapOperFcltItem = { + name: string; + cnt: number; + rt: number; +}; + +export type mapOperFcltType = { + colTitle: { + cnt: string; // 개소수 + rt: string; // 가동률 + }; + data: mapOperFcltItem[]; +}; + +export type mapReuseMapType = { + colTitle: { + reuseAmnt: string; // 하수재이용량 + cnt: string; // 시설수 + }; + data: ReuseMapContent[]; +}; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/model/map-data.ts b/apps/cm/src/pages/dashboard/model/map-data.ts new file mode 100644 index 000000000..a5eb34344 --- /dev/null +++ b/apps/cm/src/pages/dashboard/model/map-data.ts @@ -0,0 +1,65 @@ +export const basinKeyMap: { [key: string]: string } = { + "한강": "han", + "금강": "geum", + "낙동강": "nakdong", + "영산강": "yeongsan", +}; + +// --- 라벨 좌표(수동 고정값) : inset 적용 영역 기준 퍼센트 또는 px 사용 --- +// - 퍼센트: { x:0~1, y:0~1 } (권장: 반응형에 유리) +// - 픽셀: { px:number, py:number } (inset 좌상단 원점 기준) +export const MANUAL_LABEL_POS = { + han: { x: 0.52, y: 0.14 }, + geum: { x: 0.28, y: 0.47 }, + nakdong: { x: 0.64, y: 0.47 }, + yeongsan: { x: 0.18, y: 0.77 }, +}; + +export const BASIN_DATA = { + han: { + name: "한강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + geum: { + name: "금강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + nakdong: { + name: "낙동강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + yeongsan: { + name: "영산강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, +}; + +export const BASIN_IDS = [ + { key: "han", labels: ["한강", "han"] }, + { key: "geum", labels: ["금강", "geum"] }, + { key: "nakdong", labels: ["낙동강", "nakdong"] }, + { key: "yeongsan", labels: ["영산강", "yeongsan"] }, +]; + +export const svcInfo = [ + {svcId: "wr", active: true, name:"물재이용"}, + {svcId: "sn", active: false, name:"하수관로"}, + {svcId: "fo", active: false, name:"운영실태"}, +] + \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/model/sn-interface.ts b/apps/cm/src/pages/dashboard/model/sn-interface.ts new file mode 100644 index 000000000..4b86cd897 --- /dev/null +++ b/apps/cm/src/pages/dashboard/model/sn-interface.ts @@ -0,0 +1,85 @@ +import { basinKeyMap } from "../model/map-data"; +export interface PplLengthContent { + zoneNm: string; + sumPrlgVl: number; + usgCd?: string; + yearGroup?: string; +} + +export interface SewerageService { + getPplLength(ymdFrom: string, ymdTo: string, useCd: string): Promise; + getPplUseLength(ymdFrom: string, ymdTo: string, useCd: string): Promise; + getMvUsgPplLength(): Promise; + getMvOldPplLength(): Promise; +} + +export interface ChartDataSet { + label: string; + data: (number | undefined)[]; + backgroundColor: string; + borderRadius?: number; +} +export interface OldPplChartData { + labels: string[]; + datasets: ChartDataSet[]; +} + +type SnOldPplItem = { + name: string; + colTitle: string[]; + rows: number[]; +}; + +type SnOldPplLength = { + [key: string]: SnOldPplItem; +}; + +export type mapPplOldItem = { + name: string; + totalLen: number; + pplLen2025: number; + pplLen2530: number; + pplLen30: number; +}; + +export type mapPplOldType = { + colTitle: { + totalLen: string; // 총길이 + pplLen2025: string; // 20~25년 + pplLen2530: string; // 25~30년 + pplLen30: string; // 30년 이상 + }; + data: mapPplOldItem[]; +}; + + +export function buildSnOldPplLength( + chartData: { + labels: string[]; + datasets: { label: string; data: number[] }[]; + }, + totalLengths: number[] +): SnOldPplLength { + const colTitle = ["총길이", ...chartData.datasets.map((ds) => ds.label)]; + + const result: SnOldPplLength = {}; + + chartData.labels.forEach((basinName, index) => { + const key = basinKeyMap[basinName]; // geum, han, nakdong, yeongsan + if (!key) return; // 매핑 안되면 스킵 + const total = totalLengths[index] ?? 0; + const datasetValues = chartData.datasets.map( + (ds) => ds.data[index] ?? 0, + ); + result[key] = { + name: basinName, + colTitle, + rows: [ + total, // 총길이 (undefined면 0) + ...datasetValues, // 각 연차 데이터 (undefined면 0) + ], + }; + }); + + return result; +} \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/model/support-columns.tsx b/apps/cm/src/pages/dashboard/model/support-columns.tsx new file mode 100644 index 000000000..36fc75b5e --- /dev/null +++ b/apps/cm/src/pages/dashboard/model/support-columns.tsx @@ -0,0 +1,49 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import type { Support } from "../api/support-service"; + +const typeLabelMap: Record = { + waterQuality: "수질", + flood: "침수", + technical: "기술지원", +}; + +const supportTableColumns: ColumnDef[] = [ + { + accessorKey: "type", + header: "구분", + cell: ({ getValue }) => { + const value = getValue() as string; + return typeLabelMap[value] || value; + }, + }, + { + accessorKey: "total", + header: "합계", + }, + { + accessorKey: "han", + header: "한강", + }, + { + accessorKey: "nakdong", + header: "낙동강", + }, + { + accessorKey: "geum", + header: "금강", + }, + { + accessorKey: "youngsang", + header: "영산강", + }, + { + accessorKey: "ongoing", + header: "진행중", + }, + { + accessorKey: "completed", + header: "완료", + }, +]; + +export { supportTableColumns }; diff --git a/apps/cm/src/pages/dashboard/model/table-columns.tsx b/apps/cm/src/pages/dashboard/model/table-columns.tsx index aac2b2868..ea1e4f0d0 100644 --- a/apps/cm/src/pages/dashboard/model/table-columns.tsx +++ b/apps/cm/src/pages/dashboard/model/table-columns.tsx @@ -1,28 +1,9 @@ import dayjs from "dayjs"; import type { ColumnDef } from "@tanstack/react-table"; -import { Badge } from "@common/components"; -import type { Post, MyPost } from "@/entities/post"; +import type { MyPost, NoticePost } from "@/entities/post"; +import type { Schedule } from "../api/schedule-service"; -const noticePostTableColumns: ColumnDef[] = [ - { - header: "번호", - cell: ({ row, table }) => { - if (row.original.upndFixYn === "Y") { - return ( - - - - ); - } - const totalRows = table.getRowCount(); - const currentPage = table.getState().pagination.pageIndex; - const pageSize = table.getState().pagination.pageSize; - return totalRows - (currentPage * pageSize + row.index); - }, - }, +const noticePostTableColumns: ColumnDef[] = [ { accessorKey: "pstTtl", header: "제목", @@ -43,33 +24,35 @@ const noticePostTableColumns: ColumnDef[] = [ ]; const myPostTableColumns: ColumnDef[] = [ - { - header: "번호", - cell: ({ row, table }) => { - const totalRows = table.getRowCount(); - const currentPage = table.getState().pagination.pageIndex; - const pageSize = table.getState().pagination.pageSize; - return totalRows - (currentPage * pageSize + row.index); - }, - }, { accessorKey: "pstTtl", header: "제목", }, - { - accessorKey: "wrtrNm", - header: "작성자", - }, { accessorKey: "regDt", header: "작성일", cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"), }, +]; + + +const scheduleTableColumns: ColumnDef[] = [ { - accessorKey: "inqNocs", - header: "조회수", + accessorKey: "schdlTtl", + header: "주요일정", + cell: ({ row }) => { + const startDate = dayjs(row.original.schdlBgngDt).format("YYYY-MM-DD"); + const endDate = dayjs(row.original.schdlEndDt).format("YYYY-MM-DD"); + const schdlTtl = row.original.schdlTtl; + return ( +
+ [{startDate} ~ {endDate}]
{schdlTtl} +
+ ); + }, }, ]; -export { noticePostTableColumns, myPostTableColumns }; + +export { noticePostTableColumns, myPostTableColumns, scheduleTableColumns }; diff --git a/apps/cm/src/pages/dashboard/ui/banner-list.tsx b/apps/cm/src/pages/dashboard/ui/banner-list.tsx new file mode 100644 index 000000000..865540672 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/banner-list.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { useRef } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { ENDPOINT } from "@/shared/config"; +import { getImageUrlByAtflCd, getBannerList, type Banner } from "../api/banner-service"; +import { getCookie } from "@/shared/lib"; + +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, + Autoplay, +} from "@common/components"; + +type BannerImageProps = { + atflCd: string; + lnkgUrlAddr: string; +}; + +const BannerList: React.FC = () => { + const siteId = getCookie("site-id-key") ?? ""; + + const BannerImage = ({ atflCd, lnkgUrlAddr }: BannerImageProps) => { + const { data: imageUrl, isLoading } = useQuery({ + queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd], + queryFn: () => getImageUrlByAtflCd(atflCd), + enabled: !!atflCd, + }); + + const handleClick = () => { + if (lnkgUrlAddr) { + window.open(lnkgUrlAddr, "_blank"); + } + }; + + if (isLoading) { + return ( +
+
로딩 중...
+
+ ); + } + + if (!imageUrl) { + return ( +
+
이미지 없음
+
+ ); + } + + return ( +
+
+
+ ); + }; + + const { + data: bannerList, + error: bannerError, + isLoading: isLoadingBanners, + } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, siteId], + queryFn: () => getBannerList(siteId), + enabled: !!siteId, + retry: false, + }); + + const plugin = useRef(Autoplay({ delay: 2000, stopOnInteraction: true })); + + // MAIN 타입 배너만 필터링 + const mainBanners = + bannerList?.filter((banner) => banner.bnrTypeCd === "BNRMAIN") ?? []; + + // 에러 상태 판별 헬퍼 함수 + const getErrorStatus = (e: unknown): number | undefined => { + if (!e || typeof e !== "object") return undefined; + const maybe = e as { status?: number; response?: { status?: number } }; + return maybe.status ?? maybe.response?.status; + }; + + // 401 에러 처리 + if (bannerError) { + const is401Error = + (bannerError instanceof Error && bannerError.message.includes("401")) || + getErrorStatus(bannerError) === 401; + + if (is401Error) { + console.error("[BannerList] 401 인증 오류:", bannerError); + return ( +
+
+
+ 인증이 필요합니다. 로그인해주세요. +
+
+
+ ); + } + + console.error("[BannerList] 배너 조회 오류:", bannerError); + return ( +
+
+
+ 배너를 불러올 수 없습니다 +
+
+
+ ); + } + + // 로딩 중 + if (isLoadingBanners) { + return ( +
+
+
배너 로딩 중...
+
+
+ ); + } + + // siteId가 없을 때 + if (!siteId) { + return ( +
+
+
+ 사이트 ID가 없습니다 +
+
+
+ ); + } + + return ( +
+ + + {mainBanners.length > 0 ? ( + mainBanners.map((banner) => ( + + + + )) + ) : ( + +
+
+ 배너가 없습니다 +
+
+
+ )} +
+ + +
+
+ ); +}; + +export default BannerList; diff --git a/apps/cm/src/pages/dashboard/ui/basIn-select.tsx b/apps/cm/src/pages/dashboard/ui/basIn-select.tsx new file mode 100644 index 000000000..7fd7e1f42 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/basIn-select.tsx @@ -0,0 +1,42 @@ +"use client"; + +import React from "react"; +import { + cn, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components"; + +interface BasinSelectProps { + selectedBasin: string; + onValueChange: (value: string) => void; + className?: string; +} + +const BasinSelect: React.FC = ({ + selectedBasin, + onValueChange, + className + }) => { + const basins = ["한강", "금강", "낙동강", "영산강"]; + + return ( + + ); +}; + +export default BasinSelect; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/dashboard-page.tsx b/apps/cm/src/pages/dashboard/ui/dashboard-page.tsx index 76b6876fb..5f9ce3440 100644 --- a/apps/cm/src/pages/dashboard/ui/dashboard-page.tsx +++ b/apps/cm/src/pages/dashboard/ui/dashboard-page.tsx @@ -1,10 +1,11 @@ "use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; +import Image from "next/image"; import { tokenManager } from "@/entities/auth/lib/token-manager"; import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import { KoreaBasinMap } from "../lib/KoreaBasinMap"; +import { KoreaBasinMap } from "../lib/korea-basin-map"; import { Chart as ChartJS, CategoryScale, @@ -17,7 +18,18 @@ import { Tooltip, ArcElement, } from "chart.js"; -import { Doughnut } from "react-chartjs-2"; +import SnPplStatus from "./sn-ppl-status"; +import SnOldPpl from "./sn-old-ppl"; +import FoFcltCntRt from "./fo-fclt-cnt-rt"; +import FoWqltInflEffl from "./fo-wqlt-Infl-effl"; +import ReuseChart from "./reuse-chart"; +import ReuseStatus from "./reuse-status"; +import NoticeList from "./notice-list"; +import BannerList from "./banner-list"; +import SupportList from "./support-list"; +import DataStatusList from "./datastatus-list"; +import MyPostList from "./mypost-list"; +import ScheduleList from "./schedule-list"; // Chart.js 등록 ChartJS.register( @@ -42,8 +54,7 @@ interface UserInfo { function DashboardPage() { const [userInfo, setUserInfo] = useState(null); const [error, setError] = useState(""); - const router = useRouter(); - + const router = useRouter(); useEffect(() => { const fetchUserInfo = async () => { console.log("fetchUserInfo called"); @@ -102,197 +113,96 @@ function DashboardPage() { }; return ( -
+
{/* 첫번째 라인 */} -
+
{/* 첫번쨰 열 */} -
- {/* 전국 하수처리시설 가동률 */} -
-
-

전국 하수처리시설 가동률

-
-
- { - const ctx = chart.ctx; - const centerX = chart.width / 2; - const centerY = chart.height / 2; - - // 가동률 텍스트 - ctx.save(); - ctx.font = 'bold 24px Arial'; - ctx.fillStyle = '#00DFE5'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText('75%', centerX, centerY + 10); - - // 라벨 텍스트 - ctx.font = '12px Arial'; - ctx.fillStyle = '#666'; - ctx.fillText('가동률', centerX, centerY + 30); - ctx.restore(); - } - }]} - /> -
-
+
+ {/* 전국 하수처리시설 개소수 및 가동률 */} + {/* 오수관로 및 우수관로 현황 */} -
-
-

오수관로 및 우수관로 현황

-
-
-
+ - {/* 하수처리수 재이용 */} -
-
-

하수처리수 재이용

-
-
-
+ {/* 하수처리수 재이용 (m³) */} +
{/* 두번쨰 열 */} -
- {/* 하수처리시설(개소) */} -
-
-

하수처리시설(개소)

-
-
-
-
-

500㎥ 미만(개소)

-
-
3,567 개소
-
-
-
-

500㎥ 이상(개소)

-
-
767 개소
-
-
기준년월 : 2025.09
-
-
- - {/* 노후하수관로현황 */} -
-
-

노후하수관로현황

-
-
-
-
+
+ {/* 수계별 유입ㆍ방류수질 분석 */} + - {/* 재이용시설(개소) */} -
-
-

재이용시설(개소)

-
-
-
-
+ {/* 노후하수관로현황 */} + + + {/* 물재이용 현황 (재이용량, m³) */} +
- + {/* 지도영역 */} -
-
-
-

유역별 정보

-
- -
+
+
{/* 사용자 정보 카드 */} -
-

사용자 정보

+
{error &&
{error}
} {userInfo ? ( -
-
- 아이디: {userInfo.userId} +
+
+
+ 프로필 { + const target = e.target as HTMLImageElement; + target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='18' fill='%23e1e5e9'/%3E%3Ctext x='20' y='26' font-size='20' text-anchor='middle' fill='%23999'%3E%3F%3C/text%3E%3C/svg%3E"; + }} + />
-
- 이메일: {userInfo.email || userInfo.userId} -
-
- 이름: {userInfo.userName || "-"} -
-
- 2FA 활성화:{" "} - {userInfo.twoFactorEnabled ? "예" : "아니오"} +
+
{userInfo.userName || "-"}
+
온라인
+
+ 2FA 활성화:{" "} + {userInfo.twoFactorEnabled ? "예" : "아니오"} +
+
+ + +
+
) : ( !error &&
사용자 정보를 불러오는 중...
)} - - -
+ + + + +
{/* 두번째 라인 */} -
-
-
-

지원현황

-
-
- -
+
+
+
-
-
-

자료관 자료현황

-
-
-
+
+
-
-
-

공지사항

-
-
-
+
+
{/* 배너 영역 */} -
- +
+
diff --git a/apps/cm/src/pages/dashboard/ui/datastatus-list.tsx b/apps/cm/src/pages/dashboard/ui/datastatus-list.tsx new file mode 100644 index 000000000..bae89e338 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/datastatus-list.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useRef } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { ENDPOINT } from "@/shared/config"; +import { getDataStatusList, type DataStatus } from "../api/datastatus-service"; +import type { PageableList } from "@/shared/model"; + +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, + Autoplay, +} from "@common/components"; + +// 지역 타입에 따른 아이콘과 클래스 결정 +const getRegionInfo = (regionName: string) => { + if (regionName.includes("서울특별시")) { + return { icon: "🏛️", className: "special-city" }; + } + if ( + regionName.includes("광역시") || + regionName.includes("부산") || + regionName.includes("대구") || + regionName.includes("인천") || + regionName.includes("광주") || + regionName.includes("대전") || + regionName.includes("울산") + ) { + return { icon: "🏭", className: "metropolitan-city" }; + } + return { icon: "🌊", className: "province" }; +}; + +const DataStatusList: React.FC = () => { + const plugin = useRef( + Autoplay({ delay: 3000, stopOnInteraction: true }) + ); + + const { + data: dataStatusResponse, + error: dataStatusError, + isLoading: isLoadingDataStatus, + } = useQuery>({ + queryKey: [ENDPOINT.DC_SERVICE.DATA_STATUS], + queryFn: () => getDataStatusList(), + enabled: true, + retry: false, + staleTime: 5 * 60 * 1000, // 5분간 fresh 상태 유지 + refetchOnWindowFocus: false, // 윈도우 포커스 시 리페칭 방지 + refetchOnMount: false, // 마운트 시 리페칭 방지 (캐시된 데이터 사용) + }); + + const dataStatusList = dataStatusResponse?.content || []; + + // 에러 상태 판별 헬퍼 함수 + const getErrorStatus = (e: unknown): number | undefined => { + if (!e || typeof e !== "object") return undefined; + const maybe = e as { status?: number; response?: { status?: number } }; + return maybe.status ?? maybe.response?.status; + }; + + // 401 에러 처리 + if (dataStatusError) { + const is401Error = + (dataStatusError instanceof Error && dataStatusError.message.includes("401")) || + getErrorStatus(dataStatusError) === 401; + + if (is401Error) { + console.error("[DataStatusList] 401 인증 오류:", dataStatusError); + return ( +
+
+
+ 인증이 필요합니다. 로그인해주세요. +
+
+
+ ); + } + + console.error("[DataStatusList] 데이터 조회 오류:", dataStatusError); + return ( +
+
+
+ 데이터를 불러올 수 없습니다 +
+
+
+ ); + } + + // 로딩 중 + if (isLoadingDataStatus) { + return ( +
+
+
데이터 로딩 중...
+
+
+ ); + } + + return ( + <> +
+
+

자료현황

+
+
+
+
+ + + {dataStatusList.length > 0 ? ( + dataStatusList.map((dataStatus) => { + const regionInfo = getRegionInfo(dataStatus.ctpvnm); + return ( + +
+
+ + {regionInfo.icon} + + {dataStatus.ctpvnm} +
+
+
+
기본계획
+
+ {dataStatus.ctpvgnsvycnt} +
+
+
+
파일수
+
{dataStatus.filecnt}
+
+
+
+
+ ); + }) + ) : ( + +
+
+ 데이터가 없습니다 +
+
+
+ )} +
+ + + +
+
+
+ + ); +}; + +export default DataStatusList; diff --git a/apps/cm/src/pages/dashboard/ui/fo-fclt-cnt-rt.tsx b/apps/cm/src/pages/dashboard/ui/fo-fclt-cnt-rt.tsx new file mode 100644 index 000000000..2a0264e43 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/fo-fclt-cnt-rt.tsx @@ -0,0 +1,149 @@ +"use client"; +/** + * 수계별 시설운영 개소수 및 가동률 + */ +import React, { useEffect, useState, useMemo } from 'react'; +import { useStore } from "zustand"; +import BasinSelect from "./basIn-select"; +import { Doughnut } from "react-chartjs-2"; +import type { Chart as ChartJS } from "chart.js"; +// import type { mapOperFcltItem, mapBasinData } from "../model/fo-interface"; +import { OperationApi, operFcltStore, basinStore } from "../api/fo-service"; + +const FoFcltCntRt: React.FC = () => { + const [year, setYear] = useState(null); + const { selectedBasin, setSelectedBasin } = useStore(basinStore); + const operFcltStatus = useStore(operFcltStore, (state) => state.operFcltStatus); + + const onBasinChange = (value: string) => { + setSelectedBasin(value); + }; + + useEffect(() => { + const fetchData = async () => { + try { + const standardYear = await OperationApi.getStandardYear(); + setYear(standardYear); + await OperationApi.getDashOperCntRt(standardYear); + } catch (e) { + console.error("시설운영 데이터 조회 실패", e); + } + }; + fetchData(); + }, []); + + const displayData = useMemo(() => { + if (!operFcltStatus) { + return { facilityCount: 0, operatingRate: 0, chartData: [0, 100] }; + } + const basinData = operFcltStatus.filter(d => d.rvbsn === selectedBasin); // 선택된 유역의 데이터만 filter + const operatingData = basinData.find(d => d.gubun === 'R'); // 선택 유역의 R 데이터만 + const nonOperatingData = basinData.find(d => d.gubun === 'A'); + + const facilityCount = operatingData?.cnt ?? 0; + const operatingCapacity = operatingData?.fctCpc ?? 0; + const nonOperatingCapacity = nonOperatingData?.fctCpc ?? 0; + const totalCapacity = operatingCapacity + nonOperatingCapacity; + + const operatingRate = totalCapacity > 0 ? Math.round((operatingCapacity / totalCapacity) * 100) : 0; + const result = { + facilityCount, + operatingRate, + chartData: [operatingCapacity, nonOperatingCapacity], + }; + // console.log(result); + return result; + }, [operFcltStatus, selectedBasin]); + + return ( +
+
+

수계별 시설운영 개소수 및 가동률

+ +
+
+
+ {/* 왼쪽: 개소수 카드 */} +
+
+
+

개소수 ({year}년도)

+
+
+
+ {displayData.facilityCount.toLocaleString()} 개소 +
+
+
+
+ {/* 오른쪽: Doughnut 차트 */} +
+
+
+

가동률 ({year}년도)

+
+
+
+ { + const ctx = chart.ctx; + const centerX = chart.width / 2; + const centerY = chart.height / 2; + const rate = displayData.operatingRate ?? 0; + + // 가동률 텍스트 + ctx.save(); + ctx.font = 'bold 16px Arial'; + ctx.fillStyle = '#00DFE5'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(`${rate}%`, centerX, centerY + 10); + + // 라벨 텍스트 + ctx.font = '10px Arial'; + ctx.fillStyle = '#666'; + ctx.fillText('가동률', centerX, centerY + 28); + ctx.restore(); + } + }]} + /> +
+
+
+
+
+
+
+ ); + +}; + +export default FoFcltCntRt; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/fo-wqlt-Infl-effl.tsx b/apps/cm/src/pages/dashboard/ui/fo-wqlt-Infl-effl.tsx new file mode 100644 index 000000000..4c01fbdd0 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/fo-wqlt-Infl-effl.tsx @@ -0,0 +1,246 @@ +"use client"; +/** + * 수계별 유입ㆍ방류수질 분석 + */ +import React, { useEffect, useState, useMemo } from 'react'; +import { useStore } from "zustand"; +import { Line } from 'react-chartjs-2'; +import BasinSelect from "./basIn-select"; +import { + Chart as ChartJS, + CategoryScale, LinearScale, PointElement, LineElement, + Title, Tooltip, Legend, type ChartOptions, +} from 'chart.js'; +import type { OperWqltContent } from "../model/fo-interface"; +import { OperationApi, basinStore } from "../api/fo-service"; +import { defaultInitOperWqltState } from "../api/fo-service"; + +// Chart.js 등록 +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); + +type WaterQualityType = 'BOD' | 'TOC' | 'SS' | 'TP' | 'TN'; + +const FoWqltInflEffl: React.FC = () => { + const { selectedBasin, setSelectedBasin } = useStore(basinStore); + const [selectedTab, setSelectedTab] = useState('BOD'); + const [wqltData, setWqltData] = useState([]); + const tabs: WaterQualityType[] = ['BOD', 'TOC', 'SS', 'TP', 'TN']; + + const onBasinChange = (value: string) => { + setSelectedBasin(value); + }; + + useEffect(() => { + setWqltData(defaultInitOperWqltState.operWqltStatus); + const fetchData = async () => { + try { + const standardYear = await OperationApi.getStandardYear(); + const data = await OperationApi.getDashOperWqlt(standardYear); + setWqltData(data); + // console.log(wqltData); + } catch (e) { + console.error("시설 유입ㆍ방류수질 데이터 조회 실패", e); + } + }; + fetchData(); + }, []); + + const chartData = useMemo(() => { + const filteredData = wqltData + .filter(d => d.rvbsn === selectedBasin) // 해당 유역 + .sort((a, b) => Number(a.yr) - Number(b.yr)); + + const wqltKey = selectedTab.charAt(0).toUpperCase() + selectedTab.slice(1).toLowerCase(); + const inflowKey = `sumInfl${wqltKey}` as keyof OperWqltContent; + const effluentKey = `sumEffl${wqltKey}` as keyof OperWqltContent; + const inflowAvgKey = `avgInfl${wqltKey}` as keyof OperWqltContent; + const effluentAvgKey = `avgEffl${wqltKey}` as keyof OperWqltContent; + + + const labels = filteredData.map(d => d.yr ?? ''); + const inflowData = filteredData.map(d => d[inflowKey] as number); + const effluentData = filteredData.map(d => d[effluentKey] as number); + const inflowAvg = filteredData[0]?.[inflowAvgKey] as number ?? 0; + const effluentAvg = filteredData[0]?.[effluentAvgKey] as number ?? 0; + + return { labels, inflowData, effluentData, inflowAvg, effluentAvg }; + }, [wqltData, selectedBasin, selectedTab]); + + // console.log(chartData); + + // 차트 공통 옵션 + // const getChartOptions = (yAxisLabel: string): ChartOptions<'line'> => ({ + const getChartOptions = (): ChartOptions<'line'> => ({ + responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: false, + }, + plugins: { + legend: { + display: true, + position: 'bottom', + labels: { + usePointStyle: true, + boxWidth: 6, + color: '#000', + font: { + size: 8, + }, + }, + }, + tooltip: { + enabled: true, + titleFont: { size: 10 }, + bodyFont: { size: 10 }, + }, + }, + scales: { + x: { + grid: { + color: 'rgba(255, 255, 255, 0.1)', + tickLength: 4, // 눈금 길이 축소 + }, + ticks: { + color: '#000', + font: { + size: 8, // 폰트 크기 축소 + }, + padding: 0, + }, + }, + y: { + grid: { + color: 'rgba(255, 255, 255, 0.1)', + }, + ticks: { + color: '#000', + font: { + size: 8, // 폰트 크기 축소 + }, + maxTicksLimit: 4, // 눈금 개수 제한 + padding: 2, + }, + title: { + display: false, + }, + }, + }, + layout: { + padding: { + top: 5, + bottom: 0, + left: 0, + right: 5 + } + } + }); + + // 유입 데이터 차트 설정 + const inflowChartData = { + labels: chartData.labels, + datasets: [ + { + label: `유입${selectedTab} (mg/L)`, + data: chartData.inflowData, + borderColor: '#00D4FF', + backgroundColor: '#00D4FF', + borderWidth: 1, + pointRadius: 2, + pointHoverRadius: 4, + tension: 0.4, + }, + { + label: '유입 평균 (mg/L)', + data: Array(chartData.labels.length).fill(chartData.inflowAvg), + borderColor: '#FF1493', + backgroundColor: '#FF1493', + borderWidth: 1, + borderDash: [10, 5], + pointRadius: 0, + pointHoverRadius: 4, + tension: 0, + }, + ], + }; + + // 방류 데이터 차트 설정 + const effluentChartData = { + labels: chartData.labels, + datasets: [ + { + label: `방류${selectedTab} (mg/L)`, + data: chartData.effluentData, + borderColor: '#00FF88', + backgroundColor: '#00FF88', + borderWidth: 1, + pointRadius: 2, + pointHoverRadius: 4, + tension: 0.4, + }, + { + label: '방류 평균 (mg/L)', + data: Array(chartData.labels.length).fill(chartData.effluentAvg), + borderColor: '#FFB800', + backgroundColor: '#FFB800', + borderWidth: 1, + borderDash: [10, 5], + pointRadius: 0, + pointHoverRadius: 4, + tension: 0, + }, + ], + }; + + return ( +
+
+

수계별 유입ㆍ방류수질 분석

+ +
+
+
+ {/* 탭 메뉴 */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* 차트 영역 */} +
+ {/* 유입 차트 */} +
+ +
+ + {/* 방류 차트 */} +
+ +
+
+
+
+
+ + + ); +}; + +export default FoWqltInflEffl; diff --git a/apps/cm/src/pages/dashboard/ui/mypost-list.tsx b/apps/cm/src/pages/dashboard/ui/mypost-list.tsx new file mode 100644 index 000000000..2c15b7d7d --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/mypost-list.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { getCoreRowModel } from "@tanstack/react-table"; +import { PostApi, type MyPost, type NoticePost } from "@/entities/post"; +import { ENDPOINT } from "@/shared/config"; +import { useDataTable } from "@/shared/lib"; +import { + myPostTableColumns, +} from "../model/table-columns"; +import { Button } from "@common/components"; + + +const MyPostList: React.FC = () => { + const router = useRouter(); + const myPostQuery = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.MY_POSTS], + queryFn: () => + PostApi.getMyPostList({ + page: 0, + size: 5, + }), + }); + + const handleMyPostRowClick = (row: MyPost) => { + router.push(`/cms/${row.bbsTypeCd.toLowerCase()}/${row.bbsId}/${row.pstNo}`); + }; + + const { tableComponent: myPostTableComponent } = useDataTable({ + query: myPostQuery, + tableOptions: { + columns: myPostTableColumns, + getCoreRowModel: getCoreRowModel(), + }, + dataTableOptions: { + variant: "line", + onRowClick: handleMyPostRowClick + }, + + }); + + return ( +
+
+

내 게시글

+
+ {myPostTableComponent} +
+ ); +}; + +export default MyPostList; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/notice-list.tsx b/apps/cm/src/pages/dashboard/ui/notice-list.tsx new file mode 100644 index 000000000..502973225 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/notice-list.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { getCoreRowModel } from "@tanstack/react-table"; +import { PostApi, type NoticePost } from "@/entities/post"; +import { ENDPOINT } from "@/shared/config"; +import { useDataTable } from "@/shared/lib"; +import { + noticePostTableColumns, +} from "../model/table-columns"; +import { Button } from "@common/components"; + + +const NoticeList: React.FC = () => { + const router = useRouter(); + const noticePostQuery = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS], + queryFn: () => + PostApi.getNoticePostList({ + page: 0, + size: 3, + keywordType: "bbsTypeCd", + keyword: "BBSNOTICE", + }), + }); + + const handleNoticePostRowClick = (row: NoticePost) => { + router.push(`/cms/${row.bbsTypeCd.toLowerCase()}/${row.bbsId}/${row.pstNo}`); + }; + + const handleMoreClick = () => { + try { + router.push('/cms/bbsnotice/7'); + } catch (error) { + console.error("공지사항 더보기 이동 실패", error); + } + }; + + const { tableComponent: noticePostTableComponent } = useDataTable({ + query: noticePostQuery, + tableOptions: { + columns: noticePostTableColumns, + getCoreRowModel: getCoreRowModel(), + }, + dataTableOptions: { + variant: "line", + onRowClick: handleNoticePostRowClick, + }, + }); + + return ( +
+
+

공지사항

+ +
+ {noticePostTableComponent} +
+ ); +}; + +export default NoticeList; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/reuse-chart.tsx b/apps/cm/src/pages/dashboard/ui/reuse-chart.tsx new file mode 100644 index 000000000..edcc19b97 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/reuse-chart.tsx @@ -0,0 +1,242 @@ +"use client"; +/** + * 물재이용 차트 + */ +import React, { useState, useEffect, useRef } from 'react'; +import { useStore } from "zustand"; +import { Line } from 'react-chartjs-2'; +import { Button } from "@common/components"; +import type { ReuseStatusContent } from "../model/fo-interface"; +import { OperationApi, reuseStore } from "../api/fo-service"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + Filler, + type ChartOptions +} from 'chart.js'; + +// Chart.js 플러그인 등록 +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + Filler // 영역 채우기를 위해 필요 +); + + + +const ReuseChart: React.FC = () => { + const [activeIndex, setActiveIndex] = useState(0); + const intervalRef = useRef(null); + const reuseStatusData = useStore( + reuseStore, + (state) => state.reuseStatus) as ReuseStatusContent[]; + + useEffect(() => { + OperationApi.getReuseStatus().catch((e) => { + console.error("물재이용 차트 데이터 조회 실패", e); + }); + }, []); + + const labels = reuseStatusData?.map((r) => r.yr); + const sewageData = reuseStatusData?.map((r) => r.sewage); + const reclaimedData = reuseStatusData?.map((r) => r.reclaimed); + const rainwaterData = reuseStatusData?.map((r) => r.rainwater); + + const commonChartData = { + datasets: [ + { + label: '재이용량', + data: sewageData, // 기본은 하수처리수 + borderColor: '#00D4FF', // 선 색상 (밝은 하늘색) + backgroundColor: 'rgba(0, 212, 255, 0.1)', // 채우기 색상 (투명도 있는 하늘색) + borderWidth: 3, + pointBackgroundColor: '#00D4FF', + pointBorderColor: '#fff', + pointBorderWidth: 2, + pointRadius: 6, + pointHoverRadius: 8, + fill: true, // 아래 영역 채우기 활성화 + tension: 0.4, // 곡선 부드럽게 + }, + ], + }; + + const commonChartOptions: ChartOptions<'line'> = { + maintainAspectRatio: false, + plugins: { + legend: { + display: false, // 범례 숨김 (이미지에 없음) + }, + tooltip: { + callbacks: { + label: (context) => { + let label = context.dataset.label || ''; + if (label) { + label += ': '; + } + if (context.parsed.y !== null) { + label += new Intl.NumberFormat('ko-KR').format(context.parsed.y) + '㎥'; + } + return label; + } + } + } + }, + scales: { + x: { + grid: { + display: true, + color: '#f0f0f0', // 연한 회색 그리드 + }, + ticks: { + color: '#666', + } + }, + y: { + min: 1100000, // Y축 최소값 설정 (그래프 모양을 위해) + grid: { + color: '#f0f0f0', + }, + ticks: { + color: '#666', + callback: function(value) { + return new Intl.NumberFormat('ko-KR').format(Number(value)) + '㎥'; + } + } + } + }, + interaction: { + mode: 'index', + intersect: false, + }, + }; + + const chartItems = [ + { + title: "하수처리수 재이용량 (m³)", + data: { + ...commonChartData, + labels, + datasets: [{ ...commonChartData.datasets[0], data: sewageData }], + }, + options: commonChartOptions, + }, + { + title: "중수도 재이용량 (m³)", + data: { + ...commonChartData, + labels, + datasets: [{ ...commonChartData.datasets[0], data: reclaimedData }], + }, + options: commonChartOptions, + }, + { + title: "빗물 재이용량 (m³)", + data: { + ...commonChartData, + labels, + datasets: [{ ...commonChartData.datasets[0], data: rainwaterData }], + }, + options: commonChartOptions, + }, + ]; + + const startSlider = () => { + intervalRef.current = setInterval(() => { + setActiveIndex(prevIndex => (prevIndex + 1) % chartItems.length); + }, 7000); + }; + + const stopSlider = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + + useEffect(() => { + startSlider(); + return () => stopSlider(); + }, []); + + const handleIndicatorClick = (index: number) => { + stopSlider(); + setActiveIndex(index); + startSlider(); + }; + + const handleMoreClick = () => { + window.open(process.env.NEXT_PUBLIC_WR, "_blank", "noopener,noreferrer"); + }; + + const viewportRef = useRef(null); + + return ( +
+
+

{chartItems[activeIndex]?.title}

+ +
+
+
+ + {/* ✅ 좌표 기준 (절대 이동 X) */} +
+ {/* ✅ 이동 대상 */} +
+ {chartItems.map((chart, index) => ( +
+ } + /> +
+ ))} +
+
+
+ {chartItems.map((_, index) => ( +
+ +
+
+
+ ); +}; + +export default ReuseChart; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/reuse-status.tsx b/apps/cm/src/pages/dashboard/ui/reuse-status.tsx new file mode 100644 index 000000000..d1e7c4c15 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/reuse-status.tsx @@ -0,0 +1,85 @@ +"use client"; +/** + * 물재이용 현황 + */ +import React, { useEffect } from 'react'; +import { useStore } from "zustand"; +import { Button } from "@common/components"; +import type { ReuseStatusContent } from "../model/fo-interface"; +import { OperationApi, reuseStore} from "../api/fo-service"; + +const ReuseStatus: React.FC = () => { + // const [reuseStatusData, setReuseStatusData] = useState([]); + const reuseStatusData = useStore( + reuseStore, + (state) => state.reuseStatus ) as ReuseStatusContent[]; + + useEffect(() => { + OperationApi.getReuseStatus().catch((e) => { + console.error("물재이용 현황 데이터 조회 실패", e); + }); + }, []); + + + // 숫자 포맷 함수 (천단위 쉼표) + const formatNumber = (num: number): string => { + return new Intl.NumberFormat('ko-KR').format(num); + }; + + const handleMoreClick = () => { + window.open(process.env.NEXT_PUBLIC_WR, "_blank", "noopener,noreferrer"); + }; + + return ( +
+
+

물재이용 현황 (재이용량, m³)

+ +
+
+
+ + + + + + + + + + + {reuseStatusData && reuseStatusData.length > 0 && reuseStatusData?.map((row, index) => ( + + + + + + + ))} + +
+ 년도 + + 하수재이용량 + + 중수도이용량 + + 빗물이용량 +
+ {row.yr} + + {formatNumber(row.sewage)} + + {formatNumber(row.reclaimed)} + + {formatNumber(row.rainwater)} +
+
+
+
+ + + ); +}; + +export default ReuseStatus; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/schedule-list.tsx b/apps/cm/src/pages/dashboard/ui/schedule-list.tsx new file mode 100644 index 000000000..3cbfe0fe3 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/schedule-list.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { getCoreRowModel } from "@tanstack/react-table"; +import { ENDPOINT } from "@/shared/config"; +import { useDataTable } from "@/shared/lib"; +import { + scheduleTableColumns, +} from "../model/table-columns"; +import { getScheduleList } from "../api/schedule-service"; + +// 날짜를 YYYY-MM-DD 형식으로 포맷팅 +const formatDate = (date: Date): string => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +}; + +// 오늘부터 한 달 후까지의 날짜 계산 +const getDateRange = () => { + const today = new Date(); + const oneMonthLater = new Date(); + oneMonthLater.setMonth(today.getMonth() + 1); + + return { + startDate: formatDate(today), + endDate: formatDate(oneMonthLater), + }; +}; + +const ScheduleList: React.FC = () => { + const router = useRouter(); + const { startDate, endDate } = getDateRange(); + + const scheduleQuery = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, startDate, endDate], + queryFn: () => + getScheduleList({ + siteId: "", + schdlTtl: "", + schdlSeCd: "", + schdlBgngYmd: startDate, + schdlEndYmd: endDate, + reptYn: "", + commSchdlYn: "Y", + }), + }); + + + const { tableComponent: scheduleTableComponent } = useDataTable({ + query: scheduleQuery, + tableOptions: { + columns: scheduleTableColumns, + getCoreRowModel: getCoreRowModel(), + }, + dataTableOptions: { + variant: "line", + }, + }); + + return ( +
+ {scheduleTableComponent} +
+ ); +}; + +export default ScheduleList; \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx b/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx new file mode 100644 index 000000000..b1a34139c --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx @@ -0,0 +1,184 @@ +"use client"; +/** + * 노후 하수관로 현황 + */ +import React, { useEffect, useState } from "react"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + type ChartOptions, +} from "chart.js"; +import { Bar } from "react-chartjs-2"; +import { Button } from "@common/components"; +import type { PplLengthContent, ChartDataSet, OldPplChartData } from "../model/sn-interface"; +import { SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapData } from "../api/sn-service"; + + +// ChartJS 컴포넌트 등록 +ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); + +export default function SnOldPpl() { + const [oldPplLenData, setOldPplLenData] = useState([]); + const {chartData, setChartData} = useOldPplStore(); + const {setMapData} = useMapOldPplStore(); + + useEffect(() => { + // 유역별, 노후년도별 관로길이 데이터를 한번에 조회 + SewerageApi.getMvOldPplLength() + .then((apiResult) => { + setOldPplLenData(apiResult); + }) + .catch((e) => console.error("노후관로 데이터 조회 실패", e)); + }, []); + + useEffect(() => { + if (oldPplLenData.length === 0) return; + + // 유역 목록 추출 (중복 제거) + const allZones = [...new Set(oldPplLenData.map(d => d.zoneNm))]; + + // yearGroup 매핑: API 응답값 -> 차트 label + const yearGroupMap: { [key: string]: { label: string; color: string } } = { + "20년~25년미만": { label: "20-25년", color: "#3B82F6" }, + "25년~30년미만": { label: "25-30년", color: "#EF4444" }, + "30년이상": { label: "30년이상", color: "#F87171" }, + }; + + // 유역별, 노후년도별 데이터 구조화 + const dataByZone: { [zone: string]: { [yearGroup: string]: number } } = {}; + allZones.forEach(zone => { + dataByZone[zone] = {}; + }); + + oldPplLenData.forEach(item => { + let zoneData = dataByZone[item.zoneNm]; + if (!zoneData) { + zoneData = {}; + dataByZone[item.zoneNm] = zoneData; + } + if (item.yearGroup) { + zoneData[item.yearGroup] = item.sumPrlgVl; + } + }); + + // 데이터셋 생성 + const newDatasets: ChartDataSet[] = Object.entries(yearGroupMap).map(([yearGroup, { label, color }]) => ({ + label, + data: allZones.map(zone => dataByZone[zone]?.[yearGroup] ?? 0), + backgroundColor: color, + borderRadius: 4, + })); + + const newChartData: OldPplChartData = { + labels: allZones, + datasets: newDatasets, + }; + + setChartData(newChartData); + + // 차트 데이터를 기반으로 지도 팝업 데이터 생성 및 상태 업데이트 + setMapData(transformChartDataToMapData(newChartData)); + }, [oldPplLenData]); + + // 각 지역별 총 길이 계산 + const totalLengths = chartData.labels.map((label, i) => { + return chartData.datasets.reduce((sum, dataset) => { + const value = dataset.data[i]; + return sum + (typeof value === 'number' ? value : 0); + }, 0); + }); + + + // 차트 옵션 설정 + const options: ChartOptions<"bar"> = { + responsive: true, + maintainAspectRatio: false, + + scales: { + x: { + grid: { + display: true, + }, + }, + y: { + beginAtZero: true, + min: 0, + max: 10000, + ticks: { + stepSize: 2000, + callback: function (value) { + return Number(value).toLocaleString() + 'km'; + } + }, + grid: { + color: 'rgba(200, 200, 200, 0.3)', + } + } + }, + plugins: { + legend: { + position: 'top', + align: 'center', // ⬅ 가운데 정렬 + labels: { + boxWidth: 15, + padding: 20, // ⬅ 범례 항목 간 여백 확대 + usePointStyle: false, + }, + }, + tooltip: { + callbacks: { + label: function (context) { + return `${context.dataset.label}: ${Number(context.parsed.y).toLocaleString()}km`; + } + } + }, + }, + // 총 길이 데이터 표시를 위한 애니메이션 후 함수 설정 + animation: { + onComplete: function (animation) { + const chart = animation.chart; + const ctx = chart.ctx; + + // 각 데이터셋의 마지막 데이터셋의 인덱스 + if (chartData.datasets.length === 0) return; + // const lastMeta = chart.getDatasetMeta(chartData.datasets.length - 1); + // 텍스트 스타일 설정 + ctx.font = '11px Arial'; + ctx.fillStyle = '#333'; + ctx.textAlign = 'center'; + + // 각 지역별로 총 길이 표시 + chartData.labels.forEach((_, i) => { + const xPos = chart.scales.x ? chart.scales.x.getPixelForTick(i) : 0; + const yPos = chart.chartArea.top; // 차트 영역 최상단 위에 표시 + + // 막대 위에 총 길이 텍스트 표시 + if (totalLengths[i]) ctx.fillText(`총 ${totalLengths[i]?.toLocaleString()}km`, xPos, yPos); + }); + } + } + }; + + const handleMoreClick = () => { + window.open(process.env.NEXT_PUBLIC_SN, "_blank", "noopener,noreferrer"); + }; + + return ( +
+
+

노후 하수관로 현황

+ +
+
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx b/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx new file mode 100644 index 000000000..014f592c4 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx @@ -0,0 +1,172 @@ +"use client"; +/** + * 오수관로 및 우수관로 현황 + */ +import React, { useEffect, useState } from "react"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + type ChartOptions, +} from "chart.js"; +import { Bar } from "react-chartjs-2"; +import { Button } from "@common/components"; +import type { PplLengthContent } from "../model/sn-interface"; +import { SewerageApi,useUsePplStore } from "../api/sn-service"; + +interface ChartDataSet { + label: string; + data: (number | undefined)[]; + backgroundColor: string; + borderColor?: string; +} + +// const usgCd = { "SBA004": "오수관로", "SBA003": "우수관로", "ETC": "기타" }; // 용도 +ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); + +export default function SnPplStatus() { + const {chartData, setChartData} = useUsePplStore(); + const [usgPplLenData, setUsgPplLenData] = useState([]); + + + useEffect(() => { + // 유역별, 용도별 관로길이 데이터를 한번에 조회 + SewerageApi.getMvUsgPplLength() + .then((apiResult) => { + setUsgPplLenData(apiResult); + }) + .catch((e) => console.error("용도별 관로 데이터 조회 실패", e)); + }, []); + + // 데이터가 변경될 때마다 차트를 다시 계산 + useEffect(() => { + if (usgPplLenData.length === 0) return; + + // 유역 목록 추출 (중복 제거) + const allZones = [...new Set(usgPplLenData.map(d => d.zoneNm))]; + const labels = allZones; + + // 유역별, 용도별 데이터 구조화 + const dataByZone: { [key: string]: { sewage: number; stormwater: number; etc: number } } = {}; + labels.forEach(zone => { + dataByZone[zone] = { sewage: 0, stormwater: 0, etc: 0 }; + }); + + // usgCd별로 데이터 분류: SBA004(오수관로), SBA003(우수관로), ETC(기타) + usgPplLenData.forEach(item => { + if (!dataByZone[item.zoneNm]) { + dataByZone[item.zoneNm] = { sewage: 0, stormwater: 0, etc: 0 }; + } + if (item.usgCd === "SBA004") { + dataByZone[item.zoneNm]!.sewage = item.sumPrlgVl; + } else if (item.usgCd === "SBA003") { + dataByZone[item.zoneNm]!.stormwater = item.sumPrlgVl; + } else if (item.usgCd === "ETC") { + dataByZone[item.zoneNm]!.etc = item.sumPrlgVl; + } + }); + + const datasets: ChartDataSet[] = []; + + // 기타 데이터셋 (맨 아래에 쌓기 위해 먼저 추가) + datasets.push({ + label: "기타", + data: labels.map(zone => dataByZone[zone]?.etc), + backgroundColor: "rgba(255, 178, 150, 0.9)", + }); + + // 오수관로 데이터셋 + datasets.push({ + label: "오수관로", + data: labels.map(zone => dataByZone[zone]?.sewage), + backgroundColor: "rgba(180, 240, 190, 0.95)", + }); + + // 우수관로 데이터셋 + datasets.push({ + label: "우수관로", + data: labels.map(zone => dataByZone[zone]?.stormwater), + backgroundColor: "rgba(170, 210, 245, 0.95)", + }); + + setChartData({ labels, datasets }); + + }, [usgPplLenData]); + + // 총합(라벨당) 계산 + const totals = chartData.labels.map((_, i) => + chartData.datasets.reduce((s, ds) => s + (ds.data[i] as number), 0) + ); + + const options: ChartOptions<"bar"> = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: "top", + }, + tooltip: { + callbacks: { + label: (ctx) => `${ctx.dataset.label}: ${Number(ctx.parsed.y).toLocaleString()}km`, + }, + }, + }, + scales: { + x: { + stacked: true, + }, + y: { + stacked: true, + beginAtZero: true, + ticks: { + callback: (value) => `${Number(value).toLocaleString()}km`, + }, + }, + }, + // 각 스택 위에 총합 표시 + animation: { + onComplete: function (animation) { + const chart = animation.chart; + const ctx = chart.ctx; + const lastMeta = chart.getDatasetMeta(chartData.datasets.length - 1); + ctx.save(); + ctx.fillStyle = "#111"; + ctx.font = "gray bold 12px Arial"; + ctx.textAlign = "center"; + + totals.forEach((total, i) => { + const bar = lastMeta.data[i]; + if (!bar) return; + const x = bar.x; + // y는 마지막 스택의 y 좌표(스택의 최상단)에서 조금 위로 + const y = bar.y - 8; + ctx.fillText(`총 ${total.toLocaleString()}km`, x, y); + }); + + ctx.restore(); + }, + }, + }; + + const handleMoreClick = () => { + window.open(process.env.NEXT_PUBLIC_SN, "_blank", "noopener,noreferrer"); + }; + return ( +
+
+

오수관로 및 우수관로 현황

+ + +
+
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/cm/src/pages/dashboard/ui/support-list.tsx b/apps/cm/src/pages/dashboard/ui/support-list.tsx new file mode 100644 index 000000000..f62cce9b1 --- /dev/null +++ b/apps/cm/src/pages/dashboard/ui/support-list.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { getCoreRowModel } from "@tanstack/react-table"; +import { ENDPOINT } from "@/shared/config"; +import { useDataTable } from "@/shared/lib"; +import type { PageableList } from "@/shared/model"; +import { supportTableColumns } from "../model/support-columns"; +import { getSupportList, type Support } from "../api/support-service"; + +const SupportList: React.FC = () => { + const supportQuery = useQuery>({ + queryKey: [ENDPOINT.IC_SERVICE.GROUPING_STATISTICS], + queryFn: async () => { + return await getSupportList(); + }, + }); + + const { tableComponent: supportTableComponent } = useDataTable({ + query: supportQuery, + tableOptions: { + columns: supportTableColumns, + getCoreRowModel: getCoreRowModel(), + }, + dataTableOptions: { + variant: "line", + }, + }); + + return ( +
+
+

지원현황

+
+ {supportTableComponent} +
+ ); +}; + +export default SupportList; diff --git a/apps/cm/src/shared/config/api.ts b/apps/cm/src/shared/config/api.ts index 25c8b88c9..3cdb96d0c 100644 --- a/apps/cm/src/shared/config/api.ts +++ b/apps/cm/src/shared/config/api.ts @@ -44,7 +44,7 @@ export const ENDPOINT = { ORGANIZATIONS: "/cms-service/api/v1/ognzs", USER_ORGANIZATION: "/cms-service/api/v1/user-ognz", IMAGES: "/cms-service/api/v1/images", - BANNERS: "/cms-service/api/v1/bnrs", + BANNERS: "/cms-service/api/v1/bnrs/user", TEMPLATE: "/cms-service/api/v1/tmplt", TERMS: "/cms-service/api/v1/trms", LWPO_CODE: "/cms-service/api/v1/lwpo-cds", @@ -61,23 +61,38 @@ export const ENDPOINT = { APILOG: "/cms-service/api/v1/api-logs", SITE_AUTHORITY: "/cms-service/api/v1/site-authrt", CONNECTION_LOG: "/cms-service/api/v1/cntnlog", + SCEHDULES: "/cms-service/api/v1/schdls", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs", BBS_TEMPLATES: "/board-service/api/v1/bbsTmplt", POSTS: "/board-service/api/v1/posts", + MY_POSTS: "/board-service/api/v1/posts/me", POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts", POST_EVALUATION: "/board-service/api/v1/pstEvl", + SITE_AUTHORITY_LIST: "/cms-service/api/v1/site-authrt/user", + CONNECTION_LOG_LIST: "/cms-service/api/v1/cntnlog/user", }, SEWERAGE_SERVICE: { + PPL_LENGTH: "/sewerage-service/api/v1/ms/pplLength", + PPL_USE_LENGTH: "/sewerage-service/api/v1/ms/pplUseLength", PPL_COUNTS: "/sewerage-service/api/v1/ms/pplCounts", OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts", - }, + MV_USG_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvUsgPplLength", + MV_OLD_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvOldPplLength", + }, + OPERATION_SERVICE: { + MAIN_YEAR: "/operation-service/api/v1/dashBoard/main/year", + OPER_CNTRT: "/operation-service/api/v1/dashBoard/main/oper/cntrt", + OPER_WQLT: "/operation-service/api/v1/dashBoard/main/oper/wqlt", + REUSE_STATUS: "/operation-service/api/v1/dashBoard/main/reuse/status", + REUSE_MAP: "/operation-service/api/v1/dashBoard/main/reuse/map", + }, IC_SERVICE: { - GROUPING_STATISTICS: "/ic-service/api/v1/nti/grouping-statistics", + GROUPING_STATISTICS: "/ic-service/api/v1/support-status-summary", }, DC_SERVICE: { - DATA_STATUS: "/dc-service/api/v1/dc/dataStatus", + DATA_STATUS: "/dc-service/api/v1/dataStatus", }, }; diff --git a/apps/cm/src/shared/lib/match-query-status.ts b/apps/cm/src/shared/lib/match-query-status.ts index 0e5f45829..cfab150a3 100644 --- a/apps/cm/src/shared/lib/match-query-status.ts +++ b/apps/cm/src/shared/lib/match-query-status.ts @@ -1,3 +1,4 @@ +import React from "react"; import { type UseQueryResult } from "@tanstack/react-query"; import type { PageableList } from "../model"; @@ -9,24 +10,24 @@ import type { PageableList } from "../model"; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; Success: ( data: UseQueryResult> & { data: NonNullable["data"]>; }, - ) => JSX.Element; + ) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, { @@ -35,12 +36,12 @@ export function matchQueryStatus( Empty, Success, }: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element { +): React.ReactElement { if (query.isLoading && Loading) { return Loading; } diff --git a/apps/cm/src/shared/ui/page-layout-title.tsx b/apps/cm/src/shared/ui/page-layout-title.tsx new file mode 100644 index 000000000..3a1e9fef1 --- /dev/null +++ b/apps/cm/src/shared/ui/page-layout-title.tsx @@ -0,0 +1,22 @@ +import { PageTitle } from "@common/components"; +import { useMenuStore } from "../lib/use-menu-store"; + +function PageLayout({ children }: { children: React.ReactNode }) { + const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo); + + const menuPaths = currentMenuInfo?.menuPath + ?.split(">") + .map((path) => path.trim()); + + return ( +
+ + {children} +
+ ); +} + +export { PageLayout }; diff --git a/apps/cm/src/shared/ui/page-layout.tsx b/apps/cm/src/shared/ui/page-layout.tsx index 358d3b5db..8d858c80c 100644 --- a/apps/cm/src/shared/ui/page-layout.tsx +++ b/apps/cm/src/shared/ui/page-layout.tsx @@ -1,22 +1,5 @@ -import { PageTitle } from "@common/components"; -import { useMenuStore } from "../lib/use-menu-store"; - function PageLayout({ children }: { children: React.ReactNode }) { - const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo); - - const menuPaths = currentMenuInfo?.menuPath - ?.split(">") - .map((path) => path.trim()); - - return ( -
- - {children} -
- ); + return
{children}
; } export { PageLayout }; diff --git a/apps/common/features/next.config.ts b/apps/common/features/next.config.ts index 34965cf32..8a50ea611 100644 --- a/apps/common/features/next.config.ts +++ b/apps/common/features/next.config.ts @@ -1,6 +1,21 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + output: "standalone", + experimental: { + proxyTimeout: 600000, + }, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + logging: { + fetches: { + fullUrl: true, + }, + }, async rewrites() { return { afterFiles: [ diff --git a/apps/common/features/package.json b/apps/common/features/package.json index 3b782251c..14a96b6f8 100644 --- a/apps/common/features/package.json +++ b/apps/common/features/package.json @@ -28,7 +28,7 @@ "chart.js": "^4.4.8", "dayjs": "^1.11.13", "js-cookie": "^3.0.5", - "next": "15.3.0", + "next": "15.3.8", "next-themes": "^0.4.6", "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", diff --git a/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx index e518f938a..d514569e0 100644 --- a/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx @@ -4,13 +4,17 @@ import { useEffect } from "react"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; +import { getAuthLoginRedirectUrl, isAtLoginRedirectUrl } from "@common/network"; import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(getAuthLoginRedirectUrl())) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -28,8 +32,6 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - await LoginApi.silentRefresh(); - await authCheck(); } })(); }, []); diff --git a/apps/cs/.gitignore b/apps/cs/.gitignore index b63718b61..f80a96304 100644 --- a/apps/cs/.gitignore +++ b/apps/cs/.gitignore @@ -38,4 +38,5 @@ yarn-error.log* next-env.d.ts .env* -!.env.example \ No newline at end of file +!.env.example +/start_cs.log diff --git a/apps/cs/app/api/proxy/[...url]/route.ts b/apps/cs/app/api/proxy/[...url]/route.ts index 7442c702f..abd3ae007 100644 --- a/apps/cs/app/api/proxy/[...url]/route.ts +++ b/apps/cs/app/api/proxy/[...url]/route.ts @@ -60,13 +60,18 @@ async function handler( [AUTH_USER_ID]: userId, }); + const forwardedHost = request.headers.get("x-forwarded-host"); + const host = request.headers.get("host"); + const actualHost = forwardedHost || host || new URL(request.url).hostname.toString(); + // 운영 환경에서 domain 설정 + const domain = actualHost.includes("hasuon.or.kr") ? ".hasuon.or.kr" : actualHost; if (refreshToken) { cookieStore.set(REFRESH_TOKEN, refreshToken, { httpOnly: true, - sameSite: "lax", -// domain: domain, + sameSite: "strict", + domain: domain, path: "/", }); } diff --git a/apps/cs/docs/표준단어정의서.md b/apps/cs/docs/표준단어정의서.md new file mode 100644 index 000000000..6950eb84b --- /dev/null +++ b/apps/cs/docs/표준단어정의서.md @@ -0,0 +1,831 @@ +# 표준단어정의서 + +AI 이해용으로 정리된 표준단어명, 영문약어명, 영문명 목록입니다. + +| 표준단어명 | 표준단어 영문약어명 | 표준단어 영문명 | +|-----------|-------------------|----------------| +| BOD | BOD | Biochemical Oxygen Demand | +| C/N비 | CNR | Carbon-Nitrogen Ratio | +| COD | COD | Chemical Oxygen Demand | +| F/M비 | FMR | Food-Microorganism Ratio | +| GISD | GISDATA | Geographic Information System Data | +| HTML | HTML | Hypertext Markup Language | +| ID | ID | Id | +| IP | IP | Internet Protocol | +| KEY | KEY | Key | +| ND | ND | Not Detected | +| NON | NON | Non | +| NTU | NTU | Nephelometry Turbidity Unit | +| OPT | OPT | Opt | +| PAC | PAC | Polyaluminium Chloride | +| PH | PH | Hydrogen Exponent | +| PKI | PKI | Public Key Infrastructure | +| POOL | POOL | Pool | +| SS | SS | Suspended Solids (Ss) | +| TCE | TCE | Trichloroethane | +| TM | TM | Transverse Mercator | +| T-N | TN | Total Nitrogen | +| TOC | TOC | Total Organic Carbon | +| T-P | TP | Total Phosphorus | +| URL | URL | Uniform Resource Locator | +| 가구 | HSHD | Household | +| 가능 | PSBL | Possible | +| 가동 | OPR | Operation | +| 가산 | ADTN | Addition | +| 가성소다 | NAOH | Caustic Soda(Nach) | +| 가축분뇨 | ANMA | Animal Manure | +| 값 | VAL | Value | +| 강도 | IN | Intensity | +| 강우 | RAIN | Rain | +| 개방 | OPN | Opening | +| 개별 | INDIV | Individual | +| 개보수비 | RPAFEE | Repair Fee | +| 개설 | ESTBL | Establishment | +| 개시 | STRT | Start | +| 개정 | RVSN | Revision | +| 개조 | REMDEL | Remodeling | +| 개체 | INDVD | Individual | +| 개체군 | POPU | Population | +| 개폐 | RORGN | Reorganization | +| 갱신 | UPDT | Update | +| 건물 | BLDG | Building | +| 건설 | CONC | Construction | +| 건수 | NOCS | The Number Of Cases | +| 건조 | DRY | Dry | +| 검사 | INSP | Inspection | +| 검사서 | EXRPT | Examination Report | +| 검정 | RCGN | Recognition | +| 검증 | VRFC | Verification | +| 검출 | DETECT | Detection | +| 검토 | RVW | Review | +| 격자 | LATTC | Lattice | +| 결과 | RSLT | Result | +| 결의 | DCIS | Decision | +| 결정 | DCSN | Decision | +| 경계 | BNDRY | Boundary | +| 경과 | PROC | Process | +| 경도 | LOT | Longitude | +| 경보 | ALARM | Alarm | +| 경사판 | ICNTPT | Inclination plate | +| 계 | SUM | Summation | +| 계량기 | MRNR | Measuring Instrument | +| 계량기분리 | METRSPRT | Meter Separation | +| 계면 | SGLV | Sludge Level | +| 계산식 | FMUL | Formula | +| 계약 | CTRT | Contract | +| 계약기간 | CNTRPD | Contract Period | +| 계절 | SEASON | Season | +| 계획 | PLAN | Plan | +| 고도처리 | AWTR | Advanced Water Treatment | +| 고시 | ANCMNT | Announcement | +| 고액분리 | SLSPRT | Solid-Liquid Separation | +| 고형물체류시간 | SRT | Solid Retention Time | +| 공간데이터 | SPCEDATA | Space Data | +| 공공 | PUBLIC | Public | +| 공급 | SPLY | Supply | +| 공기 | AIR | Air | +| 공기여과 | ARFT | Air Filter | +| 공문 | ODOC | Official Documnet | +| 공법 | MSRC | Method Of Construction | +| 공사 | CSTRN | Construction | +| 공사비 | CONSTEXPN | Construction Expenses | +| 공업지역 | MNFTR_ARA | Manufacturing Area | +| 공용면적 | CMAR | Common Area | +| 공통 | CMMN | Commonness | +| 관 | PP | Pipe | +| 관계 | REL | Relation | +| 관련 | RLTN | Relation | +| 관로 | PPLINE | Pipe Line | +| 관리 | MNG | Management | +| 관리기관 | MGC | Management Agency | +| 관리자 | MNGR | Manager | +| 관찰 | WTCH | Watch | +| 관측 | OBSR | Observation | +| 관측소 | OBSRVT | Observatory | +| 교반기 | AGITOR | Agirator | +| 교정 | CRRC | Correction | +| 구간 | SCTN | Section | +| 구동 | DRIVE | Drive | +| 구분 | SE | Section | +| 구성 | CMPSG | Composing | +| 구역 | ZONE | Zone | +| 구조 | STRCT | Structure | +| 권역 | DSTR | District | +| 권한 | AUTH | Authority | +| 규격 | STNDRD | Standard | +| 규모 | SCAL | Scale | +| 규제 | REGL | Regulation | +| 균등 | EQLTY | Equality | +| 금액 | AMT | Amount | +| 급수 | WSP | Water Supply | +| 기간 | WHL | While | +| 기관 | INST | Institution | +| 기록 | RECD | Recording | +| 기록부 | REGI | Register | +| 기밀 | SCRET | Secret | +| 기법 | TECQU | Techniques | +| 기본 | BSIC | Basics | +| 기본정보 | BASINF | Bassinfo | +| 기본키 | PK | Primary Key | +| 기상 | WETHER | Weather | +| 기안 | DRFTG | Drafting | +| 기재 | WRIT | Write | +| 기점 | CDPNT | Cardinal Point | +| 기존 | LGCY | Legacy | +| 기준 | CRTR | Criteria | +| 기타 | ETC | Etc | +| 기한 | TERM | Term | +| 기후 | CLMT | Climate | +| 긴급 | EMRG | Emergency | +| 날개벽 | WW | Wing Wall | +| 날씨 | WETHR | Weather | +| 날짜 | DATE | DATE | +| 남조류 | BLAL | Blue-Green Algae | +| 납 | PB | Plumbum | +| 내려받기 | DWLD | Download | +| 내부반송 | ITNRCC | Internal Recycl | +| 내용 | CN | Content | +| 내용연수 | SVLF | Service Life | +| 냄새 | SMELL | Smell | +| 년 | YY | Year | +| 년간 | FAYR | For A Year | +| 노즐 | NZL | Nozzle | +| 노출 | EXPSR | Exposure | +| 농도 | DNSTY | Density | +| 농축 | TKN | Thickened | +| 누적 | ACML | Accumulate | +| 누출 | LKG | Leakage | +| 눈 | SNW | Snow | +| 단가 | UNPRC | Unit Price | +| 단기 | SRTPD | Short Period | +| 단순 | SIMPL | Simplicity | +| 단위 | UNIT | Unit | +| 대구역 | LZONE | Large Zone | +| 대분류 | LCLAS | Large Classification | +| 대장 | REGSTR | Register | +| 대장균 | COLI | Coliform, Fecal Coliform | +| 대장균군 | COLIB | Coliform Bacteria | +| 대지면적 | SIAR | Site Area | +| 대체 | RPLC | Replace | +| 덮개 | COVR | Cover | +| 데이타 | DAT | Data | +| 도구 | UNT | Unit | +| 도래 | ADVNT | Advent | +| 도로 | ROAD | Road | +| 도로명 | RN | Road Name | +| 도시 | CTY | City | +| 도엽 | MAPDMC | Map Demarcation | +| 도표 | CHART | Chart | +| 독성 | TOXCTY | Toxicity | +| 동의 | AGRE | Agreement | +| 동절기 | WSESN | The Winter Season | +| 등급 | GRD | Grade | +| 등기 | RG | Registered | +| 등록 | REG | Registration | +| 디바이스 | DVICE | Device | +| 디코딩 | DCD | Decoding | +| 레이어 | LYR | Layer | +| 로그인 | LGN | Login | +| 로우 | ROW | Row | +| 마감 | DDLN | Deadline | +| 마이크로필터 | MCRFT | Micro Filter | +| 막 | MBRN | Membrane | +| 만족도 | STFDG | Satisfaction Degree | +| 맑음 | CLAR | Clear | +| 매각 | SAL | Sale | +| 맨홀 | MH | Manhole | +| 맵 | MAP | Map | +| 메인 | MAIN | Main | +| 메일 | EMAIL | Email | +| 메탄올 | MTN | Methanol(CH3OH) | +| 명 | NM | Name | +| 모니터링 | MTRG | Monitoring | +| 모델 | MODL | Model | +| 모델링 | MODLNG | Modeling | +| 모래 | SAND | Sand | +| 목 | MOK | Mok | +| 무산소 | ANX | Anoxic | +| 무산소조 | ANX_TANK | Anoxic Tank | +| 무인센서 | MNLSENSR | Manless Sensor | +| 문서 | DCMT | Document | +| 물 | WTR | Water | +| 미급수 | NONWTRSPL | Non Water Supply | +| 미생물 | BLGC | Biological | +| 미세먼지 | PM | Particulate Matter | +| 미세목 | MCRB | Micro Bar | +| 민간위탁 | ETSVN | Entrustment To Civilian | +| 민감 | SST | Sensitive | +| 민원 | CVLCPT | Civil Complaint | +| 바이러스 | VIRS | Virus | +| 반기 | HT | Half Term | +| 반류수 | SIDSWT | Side Stream Water | +| 반송 | SNDBK | Send Back | +| 반송수 | RCCWT | Recycled Water | +| 반송슬러지 | RTNSLG | Return Sludge | +| 반영 | RFLT | Reflection | +| 반응조 | RETNK | Reactant Tank | +| 반출 | EXPRT | Export | +| 반토 | ALUM | Alumina | +| 반환 | GVBK | Giving Back | +| 받이 | INL | Inlet | +| 발생 | OCRN | Occurrence | +| 발전 | PWGNRT | Power Generation | +| 발주기관 | ORNT | Order Institution | +| 방류 | DCWTR | Discharge Water Stock Etc | +| 방류구 | DSCHG | Discharge | +| 방류수 | EFLEC | Effluence | +| 방문 | VST | Visit | +| 방문자 | VSTR | Visitor | +| 방법 | MTHD | Method | +| 방사 | RLSE | Release | +| 배관 | PLINE | Pipeline | +| 배분 | DSTRB | Distirbution | +| 배설물 | EXCRTN | Excretion | +| 배수 | DRNG | Drainage | +| 배수구역 | DRANGZN | Drainage Zone | +| 배출 | EXHST | Exhaust | +| 배출가스 | EMGA | Emission Gases | +| 배출구 | VENT | Vent | +| 배출시설 | DHGP | Discharge Equipment | +| 배출시험 | EXHTST | Exhaust Test | +| 배출자 | DCHGR | Discharge Person | +| 밸브 | VALVE | Valve | +| 번호 | NO | Number | +| 법 | LAW | Law | +| 법적 | LGL | Legal | +| 법정 | STTY | Statutory | +| 법정동 | STDG | Statutory Dong(Administrative District) | +| 법정리 | STLI | Statutory Li(Administrative District) | +| 변경 | CHG | Change | +| 변동 | FLCTN | Fluctuation | +| 변속기 | TRMIS | Transmission | +| 보고 | RPT | Report | +| 보고서 | RPOT | Report | +| 보관 | STORG | Storage | +| 보수 | MEND | Mend | +| 보유 | HOLD | Hold | +| 보정 | RVSION | Revision | +| 보조 | ASSISC | Assistance | +| 보조금 | SBSIDY | Subsidy | +| 복개 | COVE | Cover | +| 복토 | CWTH | Covering With Soil | +| 본류 | MI_RV | Main River | +| 본번 | MNO | Main Number | +| 부검 | ATPSY | Autopsy | +| 부과 | LEVY | Levy | +| 부과금 | LVYAMT | Levy Amount | +| 부기 | BUKIP | Bookkeeping | +| 부담금 | ALOTM | Allotment | +| 부번 | SNO | Sub Number | +| 부산물 | BYPRD | By-Product | +| 부서 | DEPT | Department | +| 부숙화 | DECPZ | Decomposed ~Ize | +| 부적합 | IMPPRTY | Impropriety | +| 부착 | ASN | Adhesion | +| 부품 | CMPNT | Components | +| 부하량 | LOADQTY | Load Quantity | +| 분과 | DIVSN | Division | +| 분뇨 | ENSO | Excreta, Night Soil | +| 분류 | CL | Classification | +| 분류군 | TAXON | Taxon | +| 분리막 | SPRTMBR | Separation Membrane | +| 분배 | DTRBT | Distribution | +| 분배조 | DTRBT_TANK | Distribution Tank | +| 분야 | RLM | Realm | +| 분원성 | FECL | Fecal | +| 분원성대장균군 | FCOLI | Fecal Coliform | +| 분원성연쇄상구균 | FCSTRCCI | Fecal Streptococci | +| 분할 | PRTIN | Partition | +| 비밀번호 | PSWD | Password | +| 비상 | EMGNY | Emergency | +| 비정상 | ABNRM | Abnormality | +| 사양 | SPECS | Specification | +| 사업비 | WCT | Working Cost | +| 사업장 | BPLC | Business Place | +| 사여과지 | SAND_ FILTR | Sand Filter | +| 사용 | USE | Use | +| 사용가능 | USABLE | Usable | +| 사용권 | RIOUS | Right Of Using | +| 사용량 | USG | USAGE | +| 사용자 | USER | User | +| 사위 | FDG | Four Directions Girth | +| 사유 | RESN | Reason | +| 사이클 | CYCLE | Cycle | +| 사이트 | SITE | Site | +| 사진 | PHOT | Photograph | +| 사항 | MATTR | Matter | +| 사후 | AFTFAT | After Fact | +| 삭제 | DEL | Deletion | +| 산기관 | DFS | Diffuser | +| 산업 | INDUST | Industrial | +| 산화환원전위 | ORP | Oxidation Reduction Potential | +| 살모넬라 | SLMN | Salmonella | +| 삼원촉매장치 | TWCTLC | Three Way Catalytic Converter | +| 상세 | DTL | Details | +| 상세주소 | DADDR | Detailed Address | +| 상수 | WT | Water | +| 상업지역 | CMCL_ARA | Commercial Area | +| 상태 | STUS | Status | +| 상품 | GDS | Goods | +| 상향류 | UFW | Upflow | +| 상호 | CONM | Company Name | +| 색도 | CRMTY | Chromaticity | +| 생략 | OMS | Omission | +| 생물 | LVB | Living Being | +| 생물반응조 | BIRAT | Bioreactor | +| 생물서식처 | ORGNHBT | Organism Habitat | +| 생물종 | BISP | Biological Species | +| 생성 | CRT | Creation | +| 생슬러지 | RSLG | Raw Sludge | +| 생태 | ECLGY | Ecology | +| 생태독성 | ECTOX | ecotoxicity | +| 서류 | PAPE | Papers | +| 서명 | SIGN | Signing | +| 서버 | SRVR | Server | +| 서비스 | SRVC | Service | +| 설계 | DESGN | Design | +| 설계용량 | DESGN_CPCT | Design Capacity | +| 설립 | FNDN | Foundation | +| 설정 | STNG | Setting | +| 설치 | INSTL | Installation | +| 성격 | CHTR | Character | +| 성공 | SCS | Success | +| 성능 | PRFMNC | Performance | +| 성능검사 | EFTST | Efficiency Test | +| 성적서 | SCRRP | Score Report | +| 세균 | BCLS | Bacillus | +| 세목 | FB | Fine bar | +| 세부 | DETAIL | Detail | +| 세정 | WS | Washing | +| 세정제 | CNML | Cleaning Material | +| 소 | SML | Small | +| 소독 | DSNF | Disinfection | +| 소독제 | DSINF | Disinfectant | +| 소분류 | SCLAS | Small Classification | +| 소수점 | DCPT | Decimal Point | +| 소요 | REQ | Required | +| 소유 | OWN | Own | +| 소유역 | SBASIN | Sub Basin | +| 소재지 | PLAC | Place Of Location | +| 소포 | ATFM | Antifoaming | +| 소포수 | FBW | Foam Breaking Water | +| 소포제 | ANAG | Antifoaming Agent | +| 송부 | SENDNG | Sending | +| 송신 | SNDM | Send Message | +| 송풍기 | BLW | Blower | +| 수 | CNT | Count | +| 수거 | TKAWY | Take Away | +| 수계 | WWBH | Watershed, Water Basin, Hydrosphere | +| 수동 | HOPR | Hand Operation | +| 수리학적 체류시간 | HRT | Hydraulic Retention Time | +| 수문 | Gate | Gate | +| 수산화나트륨 | SOHY | Sodium Hydroxide | +| 수소이온 | HION | Hydrogen Ion | +| 수신 | RCPTN | Reception | +| 수용가 | RCPTR | Receptor | +| 수위 | WAL | Water Level | +| 수정 | MDFCN | Modification | +| 수정자 | MDFR | Modifier | +| 수제 | SPRDIKE | Superdike | +| 수조 | Tank | Tank | +| 수질 | QTWT | Quality Of Water | +| 수질개선 | WTRQLIMP | Water Quality Improvement | +| 수질기준 | WQST | Water Quality Standards | +| 수질배출정보 | WTDI | Water Discharge Information | +| 수집 | CLCT | Collection | +| 순환 | CCL | Cycling | +| 쉬겔라 | SHIGLA | Shigella | +| 스컴 | SCM | Scum | +| 스크린 | SCR | Screen | +| 슬러지 | SLG | Sludge | +| 슬러지 밀도지수 | SDI | sludge density index | +| 슬러지 용량 지표 | SVI | Sludge Volume Index | +| 습지 | WTLN | Wetland | +| 승인 | APRV | Approval | +| 승인권 | APPRVRGT | Right Of Approval | +| 시각 | TIME | Time | +| 시간 | HR | Hour | +| 시군구 | SGG | Si/Gun/Gu | +| 시나리오 | SCEN | Scenario | +| 시도 | CTPV | Cities And Provinces | +| 시료 | SMPL | Sample | +| 시설 | FCLT | Facility | +| 시설면적 | FCAR | Facility Area | +| 시설물 | FCLTS | Facilities | +| 시스템 | SYS | System | +| 시안 | CYN | Cyaan | +| 시운전 | TSTRN | Test Run | +| 시작 | BGNG | Beginning | +| 시점 | PTM | Point Time | +| 시행 | ENFC | Enforcement | +| 시험 | TEST | Test | +| 신고 | DCLR | Declaration | +| 신고자 | DCL | Declarer | +| 신규 | NEW | New | +| 신기술 | NTCH | New Technology | +| 신뢰 | CNFDNC | Confidence | +| 신뢰성 | CRDIBLTY | Credibility | +| 실시 | OPRT | Operation | +| 실적 | ACRS | Actual Result | +| 실행 | EXCN | Execution | +| 심사 | SRNG | Screening | +| 심의 | DLBR | Deliberation | +| 아황산환원혐기성포자형성균 | SFSRA | Spore Forming Sulfite Reducing Anaerobes | +| 안정화 | STBLZT | Stabilization | +| 암롤박스(컨테이너) | ARTCT | Arm roll type container | +| 암모니아성질소 | NHN | Ammonia Nitrogen | +| 압력 | PRESSR | Pressure | +| 압송 | PUMPNG | Pumping | +| 압축 | CMPRS | Compress | +| 액비 | LQFERTLZ | Liquid Fertilizer | +| 액상 | LIQD | Liquid | +| 약 | GENTLE | Gentle | +| 약품 | CMC | Chemical | +| 약품비 | MEDFEE | Medicine Fee | +| 약품사용 | USMD | Use Medicines | +| 업로드 | UPLD | Upload | +| 업무 | DTY | Duty | +| 없음 | NOT | Nothing | +| 에너지 | ENR | Energy | +| 에틸벤젠 | ETBNZN | Ethylbenzene | +| 여과 | FILTR | Filteration | +| 여과수 | FTWT | Filtered Water | +| 여과재 | FTMTRL | Filter Material | +| 여과형 | FILTRFOM | Filter Form | +| 여부 | YN | Yes or No | +| 여시니아균 | YERSNA | Yersinia | +| 역률 | PWRER_FCT | Power Factor | +| 역세척 | BWS | Back Wash | +| 연결 | CONN | Connection | +| 연계 | LINK | Link | +| 연도 | YR | Year | +| 연락 | CNTC | Contact | +| 연령 | AGE | Age | +| 연료화 | FUELZ | Fuelize | +| 연월 | YM | Year and Month | +| 염소 | CLRN | Chlorine | +| 염소소독 | CHDI | Chlorine Disinfection | +| 염소이온 | CLION | Chlorine Ion | +| 염화제이철 | IRCLD | Iron(III) Chloride | +| 예보 | FRCST | Forecast | +| 예비 | PRPT | Preparation | +| 예산 | BGT | Budget | +| 예상 | EXPCT | Expectation | +| 예약 | RSVT | Reservation | +| 예외 | EXCPT | Exception | +| 예정 | PRNG | Prearrangement | +| 예찰 | PRDICT | Prediction | +| 오류 | ERR | Error | +| 오수받이 | HUS_INL | House Inlet | +| 오염물질 | CNTMN | Contaminant | +| 오염원 | POLUSRC | Pollution Source | +| 오존 | OZN | Ozone | +| 오프로드 | OFFRD | Off Road | +| 옥살산 | OXACID | Oxalic Acid | +| 와류 | VORTEX | Vortex | +| 와류형 | VORTXFOM | Vortex From | +| 완결 | COMPLT | Completion | +| 완료 | CMPTN | Completion | +| 완전 | PRFECT | Perfection | +| 요건 | REQSIT | Requisite | +| 요막액 | ALLNTFLD | Allantoic Fluid | +| 요청 | DMND | Demand | +| 용량 | CPCT | Capacity | +| 용역 | SERVC | Service | +| 용존산소 | DO | Dissolved Oxygen | +| 용해 | DSLV | Dissolving | +| 우량 | EXLT | Excellent | +| 우수 | ST | Storm | +| 우수받이 | STRT_INL | Street Inlet | +| 우수토구 | STM_OVFL_MUT | Storm Overflow Mouth | +| 우회 | BP | BY PASS | +| 운반 | CNVY | Conveyance | +| 운영 | OPER | Operation | +| 운영비 | OPEXP | Operation Expense | +| 운행 | OPRAT | Operation | +| 원격 | RMRT | Rmrt | +| 원격감시체계 | TMS | Tele Monitoring System | +| 원료 | MTRAL | Material | +| 원수 | ORGWT | Origin'S Water | +| 원인자 | ORGPR | The Origin Person | +| 월 | MM | Month | +| 위도 | LAT | Latitude | +| 위반 | CNTR | Contravene | +| 위촉 | ENTRST | Entrusting | +| 위치 | LOC | Location | +| 위탁 | CNSG | Consign | +| 위탁자 | CONSGNR | Consignor | +| 유기인 | ORGP | Organic Phosphorus | +| 유량 | FLUX | Flux | +| 유량조정조 | EQLZT__TANK | Equalization Tank | +| 유입 | INFlOW | Inflow | +| 유입수 | INFWTR | Influent Water | +| 유주 | SWARMG | Swarming | +| 유지 | MNTNC | Maintenance | +| 유지용수공급 | MWRS | Maintenance Water Requirement Supply | +| 유체 | FLUID | Fluid | +| 유출 | EFFLUX | Efflux | +| 유충 | LAVL | Larval | +| 유형 | TY | Type | +| 유효 | VLD | Validity | +| 음용 | DKPS | For Drinking Purpose | +| 읍면동 | EMD | Eup/Myun/Dong | +| 응답 | RSPNS | Response | +| 응집제 | COAG | Cohesive Agents | +| 이관 | TRNSF | Transfer | +| 이동 | MVMN | Movement | +| 이력 | HST | History | +| 이메일 | EML | Email | +| 이상 | ABOVE | Above | +| 이송 | TRSF | Transfer | +| 이의 | OBJC | Objection | +| 이차 | SCDR | Secondary | +| 이차침전지 | SCDR_SDMT_TANK | Secondary Sedimaentation | +| 이행 | MIG | Migratation | +| 인건비 | PEXP | Personnel Expenses{Expenditure] | +| 인계 | HNDVR | Handover | +| 인계서 | HDOVDC | Handover Document | +| 인구 | POPL | Population | +| 인산 | PHACID | Phosphoric Acid | +| 인산염인 | POP | phosphate | +| 인양기 | LFT | Lifter | +| 인원수 | NOPE | The Number of Persons | +| 인증 | CERT | Certification | +| 인처리 | PPRCS | Phosphorus Process | +| 인허가 | APVL | Approval | +| 일 | DY | Day | +| 일련번호 | SN | Serial Number | +| 일반 | GNRL | General | +| 일별 | DALY | Daily | +| 일시 | DT | Date and Time | +| 일자 | YMD | YEAR Month Day | +| 일지 | DIARY | Diary | +| 일차 | PRM | Primary | +| 일차침전지 | PST_SDMT_TANK | Primary Sedimentation Tank | +| 일평균 | DYRG | Day Average | +| 임시 | TMPR | Temporary | +| 임의 | RNDM | Random | +| 입고 | WRHS | Warehousing | +| 입력 | INPT | Input | +| 잉여 | SPS | Surplus | +| 잉여슬러지 | EC_SLG | Excess Sludge | +| 자 | MAN | Man | +| 자갈 | GRAVEL | Gravel | +| 자료 | DATA | Data | +| 자리 | DIGIT | Digit | +| 자립화 | SLFRLI | Self Reliance | +| 자연형 | NATRLFOM | Natural Form | +| 자외선 | URLI | Ultraviolet Rays, Light | +| 자재 | MTRIL | Material | +| 자체 | ITSL | Itself | +| 작성 | WRT | Writing | +| 작업 | JOB | Job | +| 잠금 | LOCK | Lock | +| 장기 | LNGTR | Long Term | +| 장소 | PLCE | Place | +| 장착 | MNTNG | Mounting | +| 재 | RE | Re | +| 재이용 | REUSE | Reuse | +| 재이용수 | | Reuse Water | +| 재활용 | RUSE | Reuse | +| 저감 | REDUC | Reduction | +| 저류조 | DTT_TANK | Detention Tank | +| 저온 | LOWTMP | Low Temperature | +| 저장 | STRG | Storage | +| 저장소 | STORGE | Storage | +| 적발 | DSCL | Disclose | +| 적산 | ADD | Addition | +| 적용 | APLCN | Application | +| 적합 | STBLT | Suitability | +| 전기 | ELCTY | Electricity | +| 전기료 | ELFEE | Electricity Fee | +| 전력 | PWRER | Power | +| 전문 | SPCY | Specially | +| 전산 | CMPUTT | Computation | +| 전송 | TRSM | Transmission | +| 전압 | VLTGE | Voltage | +| 전용 | EXUSE | Exclusive Use | +| 전원 | SELCT | Source Of Electricity | +| 전처리 | PRETR | Pretreatment | +| 점검 | CHCK | Check | +| 접속 | CNTN | Connection | +| 정밀 | PRCIS | Precision | +| 정보 | INFO | Information | +| 정산 | CLCLN | Calculation | +| 정상 | NRMLT | Normality | +| 정수장 | WPPW | Water Purification Plant, Waterworks | +| 정제 | REFINE | Refining | +| 정지 | STP | Stop | +| 제거 | RMV | Removal | +| 제수변 | SL_VALVE | Sluice Valve | +| 제작 | MNFCT | Manufacture | +| 제조사 | MKR | Maker | +| 제출 | SBMSN | Submission | +| 제한 | LIMT | Limitation | +| 조류 | ALG | Algae | +| 조목 | CSB | Coarse Bar | +| 조사 | EXMN | Examination | +| 조성 | MAKE | Make | +| 조정 | EQLT | Equalization | +| 조치 | ACTN | Action | +| 조회 | INQ | Inquiry | +| 존재 | EXST | Existence | +| 종결 | TRMN | Termination | +| 종균제 | MICBA | Microbial Agent | +| 종료 | END | End | +| 종류 | KND | Kind | +| 종목 | IEM | Item | +| 종별 | ASORT | Assortment | +| 종사자 | ENFSN | Enfilade Person | +| 종점 | TMNL | Terminal | +| 좌표 | COOR | Coordinate | +| 주간 | WIK | Week | +| 주변 | CFR | Circumference | +| 주소 | ADDR | Address | +| 주입 | IJTN | Injection | +| 주파수 | FQNC | Frequency | +| 준공 | CMCN | Completion Of Construction | +| 중권역 | MDSTRC | Medium District | +| 중복 | DPCN | Duplication | +| 중분류 | MLSFC | Middle Classification | +| 중온 | MESPH | Mesophilic | +| 중첩 | RETRTN | Reiteration | +| 중화설비 | NUTEQP | Neutralization Equipment | +| 중화제 | CNTAG | Counteragent | +| 증가 | INCRS | Increase | +| 지도점검표사본 | CITBMR | Copy of Instruction Checklist | +| 지방자치단체 | LGOV | Local Governments | +| 지번 | LOTNO | Lot Number | +| 지상 | GRND | Ground | +| 지역 | ARA | Area | +| 지질 | NTRFS | Nature Of Soil | +| 지질도 | GEOMAP | Geological Map | +| 지하 | UDGD | Underground | +| 지하수 | GROU | Groundwater | +| 지형 | TPGRPH | Topography | +| 진공 | VCM | Vacuum | +| 진단 | DGNSS | Diagnosis | +| 진동 | VIBRAT | Vibration | +| 진행 | PRGRE | Progress | +| 질산 | NIACID | Nitric Acid. | +| 질산성질소 | NO3N | Nitrate Nitrogen | +| 집계 | TOT | Total | +| 집계구 | SUMDST | Summation District | +| 집행 | IMPL | Implementation | +| 찌꺼기 | LEES | Lees | +| 차아염소산 | NAOCL | Hypochlorous Acid | +| 차집 | ITCT | Intercepting | +| 착공 | BGNCST | Begin Construction | +| 채수 | SAMWT | Sampling Of Water | +| 채수기 | WTRSMPLR | Water Sampler | +| 채수병 | WTRBOTL | Water Bottle | +| 처리 | PRCS | Process | +| 처리비 | PSSFEE | Process Fee | +| 처리수 | TRT_WT | Treated Water | +| 처리수조 | TRT_WT_TANK | Treated Water Tank | +| 처리시설 | TRTMTFCLT | Treatment Facility | +| 처리자 | PRCR | Processor | +| 처분 | DSPS | Disposition | +| 철염 | FESLT | Ferric Salt | +| 첨부 | ATCH | Attachment | +| 첨부파일 | ATFI | Attachment File | +| 초과 | EXCS | Excess | +| 초기화 | INITL | Initialization | +| 촉매 | CTLY | Catalyst | +| 총계 | TSM | Summation | +| 총대장균군 | TCOLFGRP | Total Coliform Group | +| 총량 | TQT | Total Quantity | +| 총면적 | GFA | Gross Floor Area | +| 총사업비 | TPCO | Total Project Cost | +| 총인구 | TPOPLTN | Total Population | +| 총인처리시설 | TPFCT | Total Phosphorus facility | +| 최근 | RCNT | Recent | +| 최대값 | MAXV | Maximum Value | +| 최소값 | MINV | Minimum Value | +| 최적 | BMTB | Being The Most Suitable | +| 최종 | LAST | Last | +| 최초 | FRST | First | +| 추출 | EXTR | Extraction | +| 추출정보 | EXTRINFO | Extraction Information | +| 축 | AXS | Axis | +| 출력 | OTPT | Output | +| 출발 | LVE | Leave | +| 충전기 | CHRGR | Charger | +| 취득 | ACQS | Acquisition | +| 취소 | RTRCN | Retraction | +| 측정 | MSUR | Measurement | +| 측정기 | MSRR | Measurement Instrument | +| 측정소 | MSRSTN | Measurement Station | +| 침사지 | GTCB | Grit Chamber | +| 침전 | SDMT | Sediment | +| 침전지 | SDMT_TANK | sedimentation tank | +| 컨베이어 | CVY | Conveyor | +| 컬럼 | COL | Column | +| 코드 | CD | Code | +| 크기 | SZ | Size | +| 크실렌 | XYLEN | Xylene | +| 탄화 | CARB | Carbonization | +| 탈거 | CRYOFF | Carry Off | +| 탈리액 | SPNT | Supernatant | +| 탈수 | DWTR | Dewatering | +| 탈수기 | DEHYM | Dehydration Machine | +| 탈질 | DNTFCT | Denitrificatio | +| 탈취 | DODRZ | Deodorization | +| 탈취기 | DEODRZ | Deodorizer | +| 탈취제 | DEODR | Deodorant | +| 탈황제 | DSLFA | Desulfurizing Agent | +| 터널형 | YUNLTP | Tunnel Type | +| 테이블 | TBL | Table | +| 토구 | OTFL | Outfall | +| 토지 | LAD | Land | +| 토지면적 | LDAR | Land Area | +| 통계 | STATS | Statistics | +| 통보 | NTFCTN | Notification | +| 통신 | COMM | Communication | +| 통지 | AVTSMT | Avertissement | +| 통합 | UNTY | Unity | +| 특성 | CHRT | Character | +| 특수 | SPECL | Special | +| 특정유해물질 | SHCH | Specified Hazardous Chemicals | +| 파일 | FILE | File | +| 판정 | JGMT | Judgement | +| 펌프 | PUMP | Pump | +| 펌프장 | PUMP_STT | Pump Station | +| 페놀 | PHNL | Phenol | +| 평가 | EVL | Evaluation | +| 폐기 | DSCD | Discard | +| 폐기물 | WSTE | Waste | +| 폐쇄 | CLSG | Closing | +| 폐수 | WAWA | Waste Water | +| 폐수처리 | WSWT | Wastewater Treatment | +| 폐열 | WASTHET | Waste Heat | +| 포기 | ARTN | Aeration | +| 포트 | PORT | Port | +| 폭기조 | ARTN_TANK | Aeration Tank | +| 폴리머 | PLYMR | Polymer | +| 폴리철 | FRRCSU | Ferric Sulfate | +| 표고 | AL | Altitude | +| 표기 | MARK | Mark | +| 표시 | INDCT | Indication | +| 표준 | STD | Standard | +| 표출 | EPRSS | Expression | +| 플래그 | FLAG | Flag | +| 필드 | FILD | Field | +| 필수 | ESNTL | Essential | +| 필증 | CERFIN | Certificate Of Finishing | +| 필터 | FLTER | Filter | +| 하수 | SWGE | Sewage | +| 하수도 | DRNGSYS | Drainage System | +| 하수미처리 | SEWGUNTTM | Un Sewage Treatment | +| 하수처리 | SEWGTRTM | Sewage Treatment | +| 하수처리수 | STWTR | Sewage Treatment Water | +| 하위 | LOWR | Lower | +| 하향류 | DWFW | Downflow | +| 할인 | DSCNT | Discount | +| 함수율 | MISTR_CTNT | Moisture content | +| 합계 | SMM | Summation | +| 해당 | RLVT | Relevant | +| 해석 | INTRPRTT | Interpretation | +| 해양투기 | OCDU | Ocean Dumping | +| 행망 | ADMNW | Administration Network | +| 행정 | ADMN | Administration | +| 행정구역 | ADMI | Administrative District | +| 행정동 | DONG | Dong (Administrative District) | +| 허가 | PRMSN | Permission | +| 허용 | PERM | Permission | +| 현상 | PHNMN | Phenomenon | +| 혐기 | AARB | Anaerobic | +| 혐기조 | AARB_TANK | Anaerobic tank | +| 협잡물 | CCMT | Concomitant | +| 형태 | SHAP | Shape | +| 호기 | ARB | Aerobic | +| 호기조 | ARB_TANK | Aerobic Tank | +| 호소수 | LMSHWT | Lake And Marsh Water | +| 호수 | LKE | Lake | +| 호안 | RVTMNT | Revetment | +| 호이스트 | HIST | Hoist | +| 혼합 | CMPN | Compound | +| 혼합액현탁고형물 | MLSS | Mixed liquor suspended solids | +| 혼화지 | MXG_TNAK | Mixing tank | +| 홈페이지 | HMPG | Homepage | +| 홍수 | FLOOD | Flood | +| 확인 | IDNTY | Identify | +| 확인서 | CNFRMN | Confirmation | +| 확인자 | IDFR | Identifier | +| 확장자 | EXTN | Extension | +| 확정 | CFMTN | Confirmation | +| 환경 | ENVR | Environment | +| 환경기초시설 | ENINF | Environmental Infrastructure | +| 환원 | RSTORE | Restoration | +| 활성탄 | AC | Activated Carbon | +| 활성화 | PRMT | Promote/Facilitate | +| 활용 | PRUS | Practical Use | +| 황산 | ECCBT | Encyclopaedia Britannica (H2So4) | +| 회사 | CO | Company | +| 회수 | RTRVL | Retrieval | +| 회신 | RPLY | Reply | +| 효율 | EFC | Efficiency | +| 효율성 | EFFCTV | Effectiveness | +| 휴대 | MBL | Mobile | +| 흡수정 | EXTTW | Extraction Well | +| 흡착 | ADST | Adsorption | diff --git a/apps/cs/next.config.ts b/apps/cs/next.config.ts index d75960377..61d7f72cc 100644 --- a/apps/cs/next.config.ts +++ b/apps/cs/next.config.ts @@ -5,6 +5,20 @@ const subDomain = const nextConfig: NextConfig = { output: "standalone", + experimental: { + proxyTimeout: 600000, + }, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + logging: { + fetches: { + fullUrl: true, + }, + }, async headers() { return [ { diff --git a/apps/cs/package.json b/apps/cs/package.json index 195732b19..03ec06fcf 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -25,7 +25,7 @@ "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tanstack/react-query": "^5.67.1", + "@tanstack/react-query": "^5.75.5", "@tanstack/react-query-devtools": "^5.75.5", "@tanstack/react-table": "^8.21.2", "@tinymce/tinymce-react": "^5.1.1", @@ -34,7 +34,7 @@ "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "lucide-react": "^0.474.0", - "next": "15.3.0", + "next": "15.3.8", "next-qrcode": "^2.5.1", "next-themes": "^0.4.6", "react-chartjs-2": "^5.3.0", @@ -47,17 +47,17 @@ "zustand": "^5.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3", + "@eslint/eslintrc": "^3.0.0", "@types/js-cookie": "^3.0.6", "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", - "eslint": "^9", - "eslint-config-next": "15.1.7", - "postcss": "^8", + "eslint": "^9.0.0", + "eslint-config-next": "15.4.0", + "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", "steiger": "^0.5.5", - "tailwindcss": "^3.4.1", - "typescript": "^5" + "tailwindcss": "^3.4.14", + "typescript": "^5.7.2" } -} +} \ No newline at end of file diff --git a/apps/cs/src/entities/auth/api/login-service.ts b/apps/cs/src/entities/auth/api/login-service.ts index c1968fe36..671c8bb4c 100644 --- a/apps/cs/src/entities/auth/api/login-service.ts +++ b/apps/cs/src/entities/auth/api/login-service.ts @@ -32,11 +32,22 @@ export class LoginApiService implements LoginService { }); const data = await response.json(); + console.log('data ', data) const accessToken = data[ACCESS_TOKEN]; const tokenId = data[AUTH_USER_ID]; if (!accessToken || !tokenId) { - throw new Error("Missing required authentication tokens"); + if (data.message == 'Invalid Credentials' ) { + throw new Error('아이디와 비밀번호가 일치하지 않습니다.'); + } else if (data.message == 'User not found') { + throw new Error('존재하지 않는 아이디입니다.'); + } else if (data.message == 'User is not active') { + throw new Error('활성화되지 않은 계정입니다.'); + } else if (data.message == 'User is not verified') { + throw new Error('이메일 인증이 필요합니다.'); + } else { + throw new Error(data.message); + } } tokenManager.setToken(CLAIM_NAME, accessToken); @@ -47,9 +58,9 @@ export class LoginApiService implements LoginService { ); } catch (error) { if (error instanceof AxiosError) { - throw new Error(`Login failed: ${error.response?.data?.message}`); + throw new Error(`Login 실패 ${error.response?.data?.message}`); } - throw new Error(`Login failed: ${error}`); + throw new Error(`Login 실패 ${error}`); } } diff --git a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx index c0be4c2dd..57ae75c34 100644 --- a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx @@ -1,18 +1,24 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; -import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; +import { isAtLoginRedirectUrl } from "@common/network"; +import { + ENDPOINT, + AUTH_USER_ID, + AUTH_LOGIN_REDIRECT_URL, +} from "@/shared/config"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -30,14 +36,9 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); - router.push("/auth/login"); + setUserInfo(null); + if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + window.location.assign(AUTH_LOGIN_REDIRECT_URL); } } })(); diff --git a/apps/cs/src/entities/editor/model/editor-base-config.ts b/apps/cs/src/entities/editor/model/editor-base-config.ts index bbbfe6138..ee465b675 100644 --- a/apps/cs/src/entities/editor/model/editor-base-config.ts +++ b/apps/cs/src/entities/editor/model/editor-base-config.ts @@ -6,7 +6,8 @@ export const editorBaseConfig = { // 자체 호스팅 모드 설정 - GPL 라이선스 licenseKey: "gpl", init: { - language: "ko-KR", + language: "ko_KR", + language_url: '/tinymce/langs/ko_KR.js', // 직접 호스팅한 경우 경로 지정 height: 500, // 폐쇄망 환경을 위한 설정 base_url: "/tinymce", diff --git a/apps/cs/src/entities/link/model/link-interface.ts b/apps/cs/src/entities/link/model/link-interface.ts index 244bb4c63..67cd75807 100644 --- a/apps/cs/src/entities/link/model/link-interface.ts +++ b/apps/cs/src/entities/link/model/link-interface.ts @@ -118,7 +118,7 @@ export const linkFormSchema = z.object({ last_link_rslt_cd: z.string().max(4).nullable().optional(), last_link_excn_dt: z.string().max(40).nullable().optional(), next_link_excn_dt: z.string().max(40).nullable().optional(), - last_link_err_cd: z.string().max(10).nullable().optional(), + last_link_err_cd: z.string().max(50, { message: '마지막 연계 오류 코드는 50자 이하입니다.' }).nullable().optional(), mdfcn_dt: z.string().nullable().optional(), mdfr_id: z.string().nullable().optional(), link_cycle_mi: z.coerce.number().nullable().optional().refine((val) => { diff --git a/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx b/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx index 859af4058..c49d75530 100644 --- a/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx +++ b/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { keepPreviousData, useQuery, @@ -75,7 +75,7 @@ function AddAdminDialog() { }); const query = useQuery({ - queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination, "normal-user"], + queryKey: [ENDPOINT.USER_SERVICE.USERS, "findPageUser", pagination], queryFn: () => UserApi.findPageUser({ ...userSearchQuery, @@ -181,6 +181,21 @@ function AddAdminDialog() { query.refetch(); }; + useEffect(() => { + if (!open) { + setRowSelection({}); + setSelectedGroupId(""); + setUserSearchQuery({ + keywordType: "userName", + keyword: "", + }); + setPagination({ pageIndex: 0, pageSize: 10 }); + queryClient.removeQueries({ + queryKey: [ENDPOINT.USER_SERVICE.USERS, "findPageUser"], + }); + } + }, [open]); + return ( diff --git a/apps/cs/src/pages/auth/login/ui/login-page.tsx b/apps/cs/src/pages/auth/login/ui/login-page.tsx index 4700a691b..fd1e2f809 100644 --- a/apps/cs/src/pages/auth/login/ui/login-page.tsx +++ b/apps/cs/src/pages/auth/login/ui/login-page.tsx @@ -1,20 +1,30 @@ "use client"; +import { useState } from "react"; import Image from "next/image"; import { images } from "@common/assets"; import { LoginApi } from "@/entities/auth"; import "./login.css"; function LoginPage() { + const [errorMessage, setErrorMessage] = useState(""); + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setErrorMessage(""); // 에러 메시지 초기화 const formData = new FormData(e.target as HTMLFormElement); const userId = formData.get("id") as string; const password = formData.get("password") as string; - await LoginApi.login({ userId, password }); - window.location.href = "/dashboard"; + try { + await LoginApi.login({ userId, password }); + window.location.href = "/dashboard"; + } catch (error) { + const message = + error instanceof Error ? error.message : "로그인에 실패했습니다."; + setErrorMessage(message); + } }; return ( @@ -81,6 +91,11 @@ function LoginPage() {
{/* 비밀번호 또는 아이디를 잃어버리셨나요? */}
+ {errorMessage && ( +
+ {errorMessage} +
+ )} diff --git a/apps/cs/src/pages/auth/login/ui/login.css b/apps/cs/src/pages/auth/login/ui/login.css index 831cc13a9..e8029fdb1 100755 --- a/apps/cs/src/pages/auth/login/ui/login.css +++ b/apps/cs/src/pages/auth/login/ui/login.css @@ -118,6 +118,20 @@ body { color: #327af5; font-size: 20px; } +.login .login_box .error_message { + margin: 20px 0; + padding: 12px 20px; + background: #fee; + border: 1px solid #fcc; + border-radius: 5px; + color: #c33; + font-size: 14px; + text-align: center; +} +.login .login_box .error_message span { + display: block; + font-weight: 500; +} .login .login_box .login_btn { position: relative; border-radius: 5px; diff --git a/apps/cs/src/pages/board-statistics/api/board-statistics-service.ts b/apps/cs/src/pages/board-statistics/api/board-statistics-service.ts index 03291223c..9380132e8 100644 --- a/apps/cs/src/pages/board-statistics/api/board-statistics-service.ts +++ b/apps/cs/src/pages/board-statistics/api/board-statistics-service.ts @@ -1,53 +1,22 @@ -// import { client } from "@/shared/api"; -// import { ENDPOINT } from "@/shared/config"; +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; import { type BoardStatisticsParams, type BoardStatistics, type BoardStatisticsService, } from "../model/board-statistics-interface"; -// 임시 모의 데이터 -const mockBoardStatisticsData: BoardStatistics = { - totalPosts: 2456, - totalComments: 156, - averageRating: 4.2, - totalViews: 45678, - dayList: [ - { date: "1월", posts: 10, comments: 5, views: 800 }, - { date: "2월", posts: 15, comments: 8, views: 1200 }, - { date: "3월", posts: 12, comments: 10, views: 1000 }, - { date: "4월", posts: 18, comments: 15, views: 1500 }, - { date: "5월", posts: 22, comments: 20, views: 1800 }, - { date: "6월", posts: 20, comments: 18, views: 1600 }, - { date: "7월", posts: 25, comments: 22, views: 2000 }, - ], - boardList: [ - { boardType: "notice", boardName: "공지사항", count: 45 }, - { boardType: "faq", boardName: "FAQ", count: 120 }, - { boardType: "qna", boardName: "Q&A", count: 180 }, - { boardType: "free", boardName: "자유게시판", count: 95 }, - ], - statusList: [ - { status: "completed", statusName: "답변완료", count: 75 }, - { status: "waiting", statusName: "답변대기", count: 25 }, - ], -}; - class BoardStatisticsApiService implements BoardStatisticsService { async getBoardStatistics( params: BoardStatisticsParams, ): Promise { - console.log("params: ", params); try { // 실제 API 연동 시 아래 코드 사용 - // const response = await client.get( - // `${ENDPOINT.CMS_SERVICE.BOARD_STATISTICS}`, - // { params }, - // ); - // return response.data; - - // 임시 모의 데이터 반환 - return Promise.resolve(mockBoardStatisticsData); + const response = await client.get( + `${ENDPOINT.BOARD_SERVICE.STATISTICS}`, + { params }, + ); + return response.data; } catch (error) { console.error("게시판 통계 데이터 조회 실패:", error); throw error; diff --git a/apps/cs/src/pages/board-statistics/model/board-statistics-interface.ts b/apps/cs/src/pages/board-statistics/model/board-statistics-interface.ts index 1f113475f..3d7a97c87 100644 --- a/apps/cs/src/pages/board-statistics/model/board-statistics-interface.ts +++ b/apps/cs/src/pages/board-statistics/model/board-statistics-interface.ts @@ -12,32 +12,36 @@ export interface BoardStatisticsParams extends PageableParams { } export interface BoardStatistics { - totalPosts: number; - totalComments: number; - averageRating: number; - totalViews: number; - dayList: DayStatistics[]; - boardList: BoardTypeStatistics[]; - statusList: StatusStatistics[]; + bbsTotalDto: BbsTotalDto; // 게시판 전체 통계 + pstAnsList: PstAnswerStatistics[]; // 게시물 답변 상태별 통계 목록 + bbsDailyList: BbsDailyStatistics[]; // 게시판 일별 통계 목록 + bbsPstCntList: BbsPostCountStatistics[]; // 게시판별 게시물 건수 통계 목록 } -interface DayStatistics { - date: string; - posts: number; - comments: number; - views: number; +interface BbsTotalDto { + pstInqCnt: number; // 게시물 조회 건수 + pstEvlAvg: number; // 게시물 평가 평균 + pstTotCnt: number; // 게시물 총 건수 + pstAnsWaitCnt: number; // 게시물 답변 대기 건수 } -interface BoardTypeStatistics { - boardType: string; - boardName: string; - count: number; +interface PstAnswerStatistics { + answerNm: string; // 답변 명 + answerYn: string; // 답변 여부 (Y/N) + ansCnt: number; // 답변 건수 } -interface StatusStatistics { - status: string; - statusName: string; - count: number; +interface BbsDailyStatistics { + dayYmd: string; // 일자 (YYYY-MM-DD) + pstInqCnt: number; // 게시물 조회 건수 + pstCmntCnt: number; // 게시물 댓글 건수 + pstNewCnt: number; // 게시물 신규 건수 +} + +interface BbsPostCountStatistics { + bbsId: number; // 게시판 ID + bbsNm: string; // 게시판 명 + pstCnt: number; // 게시물 건수 } export interface BoardStatisticsService { diff --git a/apps/cs/src/pages/board-statistics/model/chart-data.ts b/apps/cs/src/pages/board-statistics/model/chart-data.ts index 2a9b6671f..487d70976 100644 --- a/apps/cs/src/pages/board-statistics/model/chart-data.ts +++ b/apps/cs/src/pages/board-statistics/model/chart-data.ts @@ -11,20 +11,20 @@ export const chartData = { } => { return { data: { - labels: data.dayList.map((item) => item.date), + labels: data.bbsDailyList.map((item) => item.dayYmd), datasets: [ { type: "line", label: "신규게시물", - data: data.dayList.map((item) => item.posts), + data: data.bbsDailyList.map((item) => item.pstNewCnt), borderColor: getCssVariableValue("--chart-1"), backgroundColor: "rgba(59, 130, 246, 0.1)", tension: 0.4, }, { type: "line", - label: "답변대기", - data: data.dayList.map((item) => item.comments), + label: "댓글수", + data: data.bbsDailyList.map((item) => item.pstCmntCnt), borderColor: getCssVariableValue("--chart-2"), backgroundColor: "rgba(236, 72, 153, 0.1)", tension: 0.4, @@ -32,11 +32,10 @@ export const chartData = { { type: "line", label: "조회수", - data: data.dayList.map((item) => item.views), + data: data.bbsDailyList.map((item) => item.pstInqCnt), borderColor: getCssVariableValue("--chart-3"), backgroundColor: "rgba(168, 85, 247, 0.1)", tension: 0.4, - hidden: true, }, ], }, @@ -67,13 +66,13 @@ export const chartData = { } => { return { data: { - labels: data.boardList.map((item) => item.boardName), + labels: data.bbsPstCntList.map((item) => item.bbsNm), datasets: [ { type: "bar", label: "게시판별 분포", - data: data.boardList.map((item) => item.count), - backgroundColor: data.boardList.map((_, index) => + data: data.bbsPstCntList.map((item) => item.pstCnt), + backgroundColor: data.bbsPstCntList.map((_, index) => getCssVariableValue(`--chart-${(index % 5) + 1}`), ), }, @@ -106,12 +105,12 @@ export const chartData = { } => { return { data: { - labels: data.statusList.map((item) => item.statusName), + labels: data.pstAnsList.map((item) => item.answerNm), datasets: [ { type: "doughnut", - data: data.statusList.map((item) => item.count), - backgroundColor: data.statusList.map((_, index) => + data: data.pstAnsList.map((item) => item.ansCnt), + backgroundColor: data.pstAnsList.map((_, index) => getCssVariableValue(`--chart-${(index % 5) + 1}`), ), }, diff --git a/apps/cs/src/pages/board-statistics/ui/board-statistics-page.tsx b/apps/cs/src/pages/board-statistics/ui/board-statistics-page.tsx index 13dcbe812..716c390ab 100644 --- a/apps/cs/src/pages/board-statistics/ui/board-statistics-page.tsx +++ b/apps/cs/src/pages/board-statistics/ui/board-statistics-page.tsx @@ -26,7 +26,7 @@ import { SelectValue, } from "@common/components"; import { SiteSelect } from "@/entities/site"; -// import { ENDPOINT } from "@/shared/config"; +import { ENDPOINT } from "@/shared/config"; import { NoData } from "@/shared/ui/no-data"; import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; import { BoardStatisticsApi } from "../api/board-statistics-service"; @@ -66,7 +66,7 @@ function BoardStatisticsPage() { }); const { data: boardStatisticsData } = useQuery({ - queryKey: ["board-statistics", searchParams], + queryKey: [ENDPOINT.BOARD_SERVICE.STATISTICS, searchParams], queryFn: () => BoardStatisticsApi.getBoardStatistics({ ...searchParams, @@ -176,7 +176,8 @@ function BoardStatisticsPage() {

총 게시물

- {boardStatisticsData?.totalPosts.toLocaleString() || 0} + {boardStatisticsData?.bbsTotalDto?.pstTotCnt?.toLocaleString() || + 0}
답변대기
- {boardStatisticsData?.totalComments.toLocaleString() || 0} + {boardStatisticsData?.bbsTotalDto?.pstAnsWaitCnt?.toLocaleString() || + 0}
평균평점
- {boardStatisticsData?.averageRating.toFixed(1) || 0} + {boardStatisticsData?.bbsTotalDto?.pstEvlAvg?.toFixed(1) || 0}
총 조회수
- {boardStatisticsData?.totalViews.toLocaleString() || 0} + {boardStatisticsData?.bbsTotalDto?.pstInqCnt?.toLocaleString() || + 0}
일별 게시물 현황
- {boardStatisticsData?.dayList?.length ? ( + {boardStatisticsData?.bbsDailyList?.length ? ( ) : ( @@ -329,7 +332,7 @@ function BoardStatisticsPage() {

답변상태

- {boardStatisticsData?.statusList?.length ? ( + {boardStatisticsData?.pstAnsList?.length ? ( ) : ( @@ -342,7 +345,7 @@ function BoardStatisticsPage() {

게시판별 분포

- {boardStatisticsData?.boardList?.length ? ( + {boardStatisticsData?.bbsPstCntList?.length ? ( ) : ( diff --git a/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx b/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx index d21f5b60a..3179303dd 100644 --- a/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx +++ b/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx @@ -1,4 +1,4 @@ -import { memo } from "react"; +import { memo, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery, useQueryClient } from "@tanstack/react-query"; @@ -140,6 +140,10 @@ const LwpoCodeManagementDialog = memo( } }; + useEffect(() => { + if (!open) form.reset(); + }, [open]); + return ( {children} diff --git a/apps/cs/src/pages/code/ui/upcode-dialog.tsx b/apps/cs/src/pages/code/ui/upcode-dialog.tsx index fafb7429f..6774bd17e 100644 --- a/apps/cs/src/pages/code/ui/upcode-dialog.tsx +++ b/apps/cs/src/pages/code/ui/upcode-dialog.tsx @@ -1,4 +1,4 @@ -import { memo } from "react"; +import { memo, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -96,6 +96,10 @@ const UpCodeManagementDialog = memo( if (await confirm(confirmMessage)) callback(data); }; + useEffect(() => { + if (!open) form.reset(); + }, [open]); + return ( {children} diff --git a/apps/cs/src/pages/dashboard/api/dashboard-service.ts b/apps/cs/src/pages/dashboard/api/dashboard-service.ts index 94bf807a1..998215864 100644 --- a/apps/cs/src/pages/dashboard/api/dashboard-service.ts +++ b/apps/cs/src/pages/dashboard/api/dashboard-service.ts @@ -1,6 +1,13 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { Dashboard, DashboardService } from "../model/dashboard-interface"; +import type { + Dashboard, + DashboardService, + NoticeUser, + Schedule, + ScheduleQueryParams, +} from "../model/dashboard-interface"; +import type { PageableList } from "@/shared/model"; export class DashboardApiService implements DashboardService { public async getDashboard(): Promise { @@ -9,6 +16,25 @@ export class DashboardApiService implements DashboardService { ); return response.data; } + + public async getNoticeUserList( + recvUnqId: string, + ): Promise> { + const response = await client.get>( + `${ENDPOINT.CMS_SERVICE.NOTICE_USER}/${recvUnqId}/list`, + ); + return response.data; + } + + public async getScheduleList( + params?: ScheduleQueryParams, + ): Promise { + const response = await client.get( + ENDPOINT.CMS_SERVICE.SCHEDULES, + { params }, + ); + return response.data; + } } export const DashboardApi = new DashboardApiService(); diff --git a/apps/cs/src/pages/dashboard/model/dashboard-interface.ts b/apps/cs/src/pages/dashboard/model/dashboard-interface.ts index 9e038eef7..e2a01173d 100644 --- a/apps/cs/src/pages/dashboard/model/dashboard-interface.ts +++ b/apps/cs/src/pages/dashboard/model/dashboard-interface.ts @@ -1,3 +1,5 @@ +import type { PageableList } from "@/shared/model"; + export interface SiteStatisticsContent { siteId: string; siteNm: string; @@ -27,6 +29,42 @@ export interface Dashboard { menuAccessList: MenuStatisticsContent[]; } +export interface NoticeUser { + notiId: number; + recvUnqId: string; + senderUnqId: string; + notiType: string; + notiTtl: string; + notReadYn: string; + regDt: string; +} + +export interface Schedule { + schdlBgngDt: string; + schdlEndDt: string; + actualDate: string; + schdlSn: number; + siteId: string; + schdlTtl: string; + schdlSeCd: string; + schdlCn: string; + reptYn: string; + commSchdlYn: string; + atflCd: string | null; +} + +export interface ScheduleQueryParams { + siteId?: string; + schdlTtl?: string; + schdlSeCd?: string; + schdlBgngYmd?: string; + schdlEndYmd?: string; + reptYn?: string; + commSchdlYn?: string; +} + export interface DashboardService { getDashboard(): Promise; + getNoticeUserList(recvUnqId: string): Promise>; + getScheduleList(params?: ScheduleQueryParams): Promise; } diff --git a/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx index 65fd0030a..d787a6ab7 100644 --- a/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx +++ b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx @@ -1,5 +1,6 @@ "use client"; +import dayjs from "dayjs"; import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { getCoreRowModel } from "@tanstack/react-table"; @@ -17,8 +18,8 @@ import { } from "chart.js"; import { Bar, Line } from "react-chartjs-2"; import { PostApi, type RecentPost } from "@/entities/post"; -import { ENDPOINT } from "@/shared/config"; -import { useDataTable } from "@/shared/lib"; +import { AUTH_USER_ID, ENDPOINT, SITE_ID } from "@/shared/config"; +import { getCookie, useDataTable } from "@/shared/lib"; import { DashboardApi } from "../api/dashboard-service"; import { chartData } from "../model/chart-data"; import { @@ -39,12 +40,44 @@ ChartJS.register( ); function DashboardPage() { + const userId = getCookie(AUTH_USER_ID) ?? ""; + const router = useRouter(); + const { data } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.DASHBOARD], queryFn: () => DashboardApi.getDashboard(), }); + const { data: noticeUserList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.NOTICE_USER], + queryFn: () => DashboardApi.getNoticeUserList(userId), + enabled: !!userId, + select: (data) => { + // 오늘 ~ 한 달 전 사이의 일정 + const today = dayjs().endOf("day"); + const monthAgo = dayjs().subtract(1, "month").startOf("day"); + return data.content?.filter((item) => { + const regDt = dayjs(item.regDt); + return ( + (regDt.isSame(monthAgo) || regDt.isAfter(monthAgo)) && + (regDt.isSame(today) || regDt.isBefore(today)) + ); + }); + }, + }); + + const { data: scheduleList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.SCHEDULES], + queryFn: () => + DashboardApi.getScheduleList({ + siteId: SITE_ID, + schdlBgngYmd: dayjs().format("YYYY-MM-DD"), + schdlEndYmd: dayjs().add(1, "month").format("YYYY-MM-DD"), + commSchdlYn: "Y", + }), + }); + const noticePostQuery = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS], queryFn: () => @@ -323,22 +356,15 @@ function DashboardPage() {
-
- 신규 사용자 가입 - {(1).toLocaleString()}분 전 -
-
- 메뉴 수정 요청 - {(5).toLocaleString()}분 전 -
-
- 게시물 신고 - {(10).toLocaleString()}분 전 -
-
- 컨텐츠 승인 요청 - {(15).toLocaleString()}분 전 -
+ {noticeUserList?.map((item) => ( +
+ {item.notiTtl} + {dayjs(item.regDt).fromNow()} +
+ ))}
@@ -349,24 +375,44 @@ function DashboardPage() {
-
- - {(3).toLocaleString()}월 정기 시스템 점검 - - 내일 -
-
- 서버 업데이트 예정 - {(3).toLocaleString()}일 후 -
-
- 백업 스케줄 - {(5).toLocaleString()}일 후 -
-
- 정기 보안 점검 - {(7).toLocaleString()}일 후 -
+ {scheduleList && scheduleList.length > 0 ? ( + scheduleList.map((schedule, index) => { + const today = dayjs(); + const scheduleDate = dayjs(schedule.actualDate); + const daysUntil = scheduleDate.diff(today, "day"); + + let timeText = ""; + if (daysUntil === 0) { + timeText = "오늘"; + } else if (daysUntil === 1) { + timeText = "내일"; + } else if (daysUntil > 1) { + timeText = `${daysUntil}일 후`; + } else { + timeText = scheduleDate.format("YYYY-MM-DD"); + } + + return ( +
+ + {schedule.schdlTtl} + + {timeText} +
+ ); + }) + ) : ( +
+ 예정된 일정이 없습니다. +
+ )}
diff --git a/apps/cs/src/pages/groups/ui/menu-authority-dialog.tsx b/apps/cs/src/pages/groups/ui/menu-authority-dialog.tsx index 38c6597fc..cde3cf641 100644 --- a/apps/cs/src/pages/groups/ui/menu-authority-dialog.tsx +++ b/apps/cs/src/pages/groups/ui/menu-authority-dialog.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -48,25 +48,14 @@ function MenuAuthorityDialog({ groupId }: MenuAuthorityDialogProps) { keywordType: "", siteId: "", }); - - const handleReset = () => { - if (inputRef.current) inputRef.current.value = ""; - setKeywordType(""); - setSiteId(""); - setSearchParams({ - keyword: "", - keywordType: "", - siteId: "", - }); - }; - const query = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENU_AUTHRTS, groupId, searchParams], - queryFn: () => MenuAuthorityApi.getGroupMenuAuthorityList({ - groupId, - ...searchParams - }), + queryFn: () => + MenuAuthorityApi.getGroupMenuAuthorityList({ + groupId, + ...searchParams, + }), enabled: open && !!groupId, }); @@ -117,6 +106,21 @@ function MenuAuthorityDialog({ groupId }: MenuAuthorityDialogProps) { }); }; + const handleReset = () => { + if (inputRef.current) inputRef.current.value = ""; + setSiteId(""); + setKeywordType(""); + setSearchParams({ + keyword: "", + keywordType: "", + siteId: "", + }); + }; + + useEffect(() => { + if (!open) handleReset(); + }, [open]); + return ( diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx index 7ae7990a1..d94d79c9a 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -39,10 +39,10 @@ function LoginlogPage() { const [presetDateRange, setPresetDateRange] = useState(""); const [dateRange, setDateRange] = useState(defaultDateRange); const [userId, setUserId] = useState(""); - const [errOcrnYn, setErrOcrnYn] = useState("N"); + const [errOcrnYn, setErrOcrnYn] = useState("ALL"); const [searchParams, setSearchParams] = useState({ userId: "", - errOcrnYn: "N", + errOcrnYn: "", searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), searchDateTo: dayjs().format("YYYY-MM-DD"), sort: "lgnDt,desc", @@ -87,7 +87,7 @@ function LoginlogPage() { setSearchParams({ ...searchParams, userId, - errOcrnYn, + errOcrnYn: errOcrnYn === "ALL" ? "" : errOcrnYn, searchDateFrom: dateRange.from ? dayjs(dateRange.from).format("YYYY-MM-DD") : "", @@ -102,12 +102,12 @@ function LoginlogPage() { const handleReset = () => { setUserId(""); - setErrOcrnYn("N"); + setErrOcrnYn("ALL"); setDateRange(defaultDateRange); setSearchParams({ ...searchParams, userId: "", - errOcrnYn: "N", + errOcrnYn: "", searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), searchDateTo: dayjs().format("YYYY-MM-DD"), }); @@ -180,6 +180,7 @@ function LoginlogPage() { + 전체 정상 오류 diff --git a/apps/cs/src/pages/module/ui/management-authority-dialog.tsx b/apps/cs/src/pages/module/ui/management-authority-dialog.tsx index 0459ba804..b3e6f3fe5 100644 --- a/apps/cs/src/pages/module/ui/management-authority-dialog.tsx +++ b/apps/cs/src/pages/module/ui/management-authority-dialog.tsx @@ -1,4 +1,4 @@ -import { useState, memo } from "react"; +import { useState, memo, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -90,6 +90,10 @@ const ManagementAuthorityDialog = memo( } }; + useEffect(() => { + if (!open) form.reset(); + }, [open]); + return ( diff --git a/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx b/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx index 5377f6a7b..e327fb88e 100644 --- a/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx +++ b/apps/cs/src/pages/role/ui/mapping-role-dialog.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table"; import { @@ -113,6 +113,10 @@ function MappingRoleDialog({ }, }); + useEffect(() => { + if (!open) handleReset(); + }, [open]); + return ( diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index ce6f61381..db8eda72c 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -56,6 +56,7 @@ export const ENDPOINT = { APILOG: "/cms-service/api/v1/api-logs", SITE_AUTHORITY: "/cms-service/api/v1/site-authrt", CONNECTION_LOG: "/cms-service/api/v1/cntnlog", + NOTICE_USER: "/cms-service/api/v1/noti/user", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs", @@ -63,6 +64,7 @@ export const ENDPOINT = { POSTS: "/board-service/api/v1/posts", POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts", POST_EVALUATION: "/board-service/api/v1/pstEvl", + STATISTICS: "/board-service/api/v1/stat/bbs", }, }; diff --git a/apps/cs/src/shared/config/env.ts b/apps/cs/src/shared/config/env.ts index c6373724f..95d1a4477 100644 --- a/apps/cs/src/shared/config/env.ts +++ b/apps/cs/src/shared/config/env.ts @@ -2,6 +2,8 @@ export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; +export const AUTH_LOGIN_REDIRECT_URL = + process.env.NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL || "/auth/login"; export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS"; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = diff --git a/apps/cs/src/shared/lib/match-query-status.ts b/apps/cs/src/shared/lib/match-query-status.ts index 0e5f45829..408b03388 100644 --- a/apps/cs/src/shared/lib/match-query-status.ts +++ b/apps/cs/src/shared/lib/match-query-status.ts @@ -1,3 +1,4 @@ +import type React from "react"; import { type UseQueryResult } from "@tanstack/react-query"; import type { PageableList } from "../model"; @@ -9,24 +10,24 @@ import type { PageableList } from "../model"; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; Success: ( data: UseQueryResult> & { data: NonNullable["data"]>; }, - ) => JSX.Element; + ) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, { @@ -35,12 +36,12 @@ export function matchQueryStatus( Empty, Success, }: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element { +): React.ReactElement { if (query.isLoading && Loading) { return Loading; } diff --git a/apps/dc/package.json b/apps/dc/package.json index 2aa9ad781..747cdf91e 100644 --- a/apps/dc/package.json +++ b/apps/dc/package.json @@ -16,14 +16,15 @@ "@minoru/react-dnd-treeview": "^3.5.0", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-slot": "^1.1.2", - "@tanstack/react-query": "^5.67.1", + "@tanstack/react-query": "^5.75.5", "@tanstack/react-table": "^8.21.2", "axios": "^1.8.1", "chart.js": "^4.4.8", "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "lucide-react": "^0.474.0", - "next": "15.2.3", + "next": "15.3.8", + "picocolors": "^1.1.1", "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", "react-dropzone": "^14.3.8", @@ -34,18 +35,18 @@ "zustand": "^5.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3", + "@eslint/eslintrc": "^3.0.0", "@faker-js/faker": "^9.5.0", "@types/js-cookie": "^3.0.6", "@types/node": "^22.17.0", "@types/webpack": "^5.28.5", - "eslint": "^9", - "eslint-config-next": "15.2.3", - "postcss": "^8", + "eslint": "^9.0.0", + "eslint-config-next": "15.4.0", + "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", "steiger": "^0.5.5", - "tailwindcss": "^3.4.1", - "typescript": "^5" + "tailwindcss": "^3.4.14", + "typescript": "^5.7.2" } } \ No newline at end of file diff --git a/apps/dc/src/entities/auth/lib/auth-wrapper.tsx b/apps/dc/src/entities/auth/lib/auth-wrapper.tsx index 1f196ed52..57ae75c34 100644 --- a/apps/dc/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/dc/src/entities/auth/lib/auth-wrapper.tsx @@ -1,20 +1,24 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; -import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; - -const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +import { isAtLoginRedirectUrl } from "@common/network"; +import { + ENDPOINT, + AUTH_USER_ID, + AUTH_LOGIN_REDIRECT_URL, +} from "@/shared/config"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -32,14 +36,9 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); - router.push("/auth/login"); + setUserInfo(null); + if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + window.location.assign(AUTH_LOGIN_REDIRECT_URL); } } })(); diff --git a/apps/dc/src/pages/datainsert/ui/datainsert-page.tsx b/apps/dc/src/pages/datainsert/ui/datainsert-page.tsx index c1e6024a0..08e2530ef 100644 --- a/apps/dc/src/pages/datainsert/ui/datainsert-page.tsx +++ b/apps/dc/src/pages/datainsert/ui/datainsert-page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { PageLayout } from "@/shared/ui/page-layout"; import { Button } from "@common/components"; import { @@ -233,7 +233,7 @@ function DataInsertPage() { }; // 단순 트리 컴포넌트 (펼침/접기 지원) - function SimpleTree({ nodes, selectedId, onSelect, depth = 0 }: { nodes: any[]; selectedId: string; onSelect: (node: any) => void; depth?: number }): JSX.Element { + function SimpleTree({ nodes, selectedId, onSelect, depth = 0 }: { nodes: any[]; selectedId: string; onSelect: (node: any) => void; depth?: number }): React.ReactElement { return (
    ( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; Success: ( data: UseQueryResult> & { data: NonNullable["data"]>; }, - ) => JSX.Element; + ) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element; +): React.ReactElement; export function matchQueryStatus( query: UseQueryResult>, { @@ -35,12 +36,12 @@ export function matchQueryStatus( Empty, Success, }: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: React.ReactElement; + Errored?: React.ReactElement | ((error: unknown) => React.ReactElement); + Empty?: React.ReactElement; + Success: (data: UseQueryResult>) => React.ReactElement; }, -): JSX.Element { +): React.ReactElement { if (query.isLoading && Loading) { return Loading; } diff --git a/apps/dm/.env.local b/apps/dm/.env.local index 93ea58f01..88b6f8067 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -4,19 +4,19 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_PORT = 3005 -#NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035 NEXT_PUBLIC_PROXY_HOST = http://localhost:3005 -NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +# NEXT_PUBLIC_FO_SERVER_API_URL = http://192.168.1.222:8883/operation-service/api/v1 +NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API +NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login diff --git a/apps/dm/.env.mindone b/apps/dm/.env.mindone index 1ca3ecfb6..da6f80177 100644 --- a/apps/dm/.env.mindone +++ b/apps/dm/.env.mindone @@ -14,6 +14,7 @@ NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/ap NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API +NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API # NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login diff --git a/apps/dm/.env.production b/apps/dm/.env.production index 5aeaa9c45..5a401c89a 100644 --- a/apps/dm/.env.production +++ b/apps/dm/.env.production @@ -15,7 +15,8 @@ NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service -NEXT_PUBLIC_NGII_URL = http://172.29.143.87 +NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 +NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx index 22418c7b7..35836d07c 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx @@ -87,9 +87,9 @@ export default function ModalSimilarPage({ sply = 0, usageCd = '', money = 0 }: }, [getSimilarCaseList, money, searchUseType, sply, usageCd]); return ( -
    +
    - <div className="overflow-x-auto h-[450px] w-full mt-8" tabIndex={0} aria-label="유사사례 테이블 영역"> + <div className="overflow-x-auto h-[480px] w-full mt-8" tabIndex={0} aria-label="유사사례 테이블 영역"> <div className="w-[150%]"> <div className="card-header justify-self-start"> <h3 className="text-2xl font-medium text-label">유사사례</h3> @@ -148,7 +148,7 @@ export default function ModalSimilarPage({ sply = 0, usageCd = '', money = 0 }: </div> </div> </div> - <div className="overflow-x-auto h-[270px] w-full mt-8"> + <div className="overflow-x-auto h-[270px] w-full mt-8 overflow-y-hidden"> <div className="w-[130%]"> <UiTable headName={"사업비 상세정보"} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index b6a395974..df418b20f 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -1,5 +1,11 @@ "use client"; +/** + * 수요정보등록 화면 + * + * 🔗 사용 API + * - 수요정보등록: POST /reuseFc/request/demandInfo + */ import { useDmStore } from "@/app/store"; import { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } from "@/app/store/sdmMng"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; @@ -60,7 +66,7 @@ function SdmInfoInsertContent() { ...prev, sdmNm: searchParams.get("dmdCoNm") || "", })); - if(searchParams.get("dmdInfoId")){ + if (searchParams.get("dmdInfoId")) { // const data = { // dmdCoNm: searchParams.get("dmdCoNm") || '', // ctpvCd: searchParams.get("ctpvCd") || '', @@ -69,8 +75,8 @@ function SdmInfoInsertContent() { // daddr: searchParams.get("daddr") || '', // telno: searchParams.get("telno") || '', // } - - // sessionStorage.setItem("user", JSON.stringify(data || {})); + + // sessionStorage.setItem("user", JSON.stringify(data || {})); } //sessionStorage.setItem("user", JSON.parse(user||'') || ""); setIsLogin(user || searchParams.get("dmdInfoId") ? true : false); @@ -121,7 +127,7 @@ function SdmInfoInsertContent() { const userData = JSON.parse(user); if (userData.dmdCoNm) { setFacilityName(userData.dmdCoNm); - form.setValue("sdmNm", userData.dmdCoNm); + form.setValue("sdmNm", userData.dmdCoNm); // 수요처명(회사명) } } catch (error) { console.error("Error parsing user data:", error); @@ -203,10 +209,10 @@ function SdmInfoInsertContent() { tph: Number(data.tph) || null, trbt: Number(data.trbt) || null, ph: data.ph || "", - blgcOxygDmndQnt: Number(data.bod) ||null, + blgcOxygDmndQnt: Number(data.bod) || null, clIonQnt: Number(data.clIonQnt) || null, smll: data.smll || "", - cdtnv: Number(data.cdtnv) ||null, + cdtnv: Number(data.cdtnv) || null, crmty: Number(data.crmty) || null, rmrk: data.rmrk || "", bzmnNo: "", diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx new file mode 100644 index 000000000..2c682bdbf --- /dev/null +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx @@ -0,0 +1,22 @@ +"use client"; +/** + * 수요처 로그아웃 화면 + * + * 🔗 사용 API + * + */ +import { useDmStore } from "@/app/store"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +export default function SdmLogout() { + const router = useRouter(); + const { actionLogout } = useDmStore((state) => state); + + useEffect(() => { + actionLogout(); + router.push("/pages/sdmMng/sdmInfoInsert"); + }, [actionLogout, router]); + + return null; +} diff --git a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx index e6b6da4d6..f8d9704d4 100644 --- a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx @@ -28,11 +28,12 @@ export default function WrStatusPage() { const { Common, dmStatus, getDmStatusData,initializeCommonData} = useDmStore((state) => state); - const { statusData,facilityTotals ,chartData} = dmStatus; + const { year:defaultYear, statusData,facilityTotals ,chartData} = dmStatus; const [selectedRegion, setSelectedRegion] = useState<string | null>(null); const [hoveredRegion, setHoveredRegion] = useState<string | null>(null); const [tooltipPosition, setTooltipPosition] = useState<{ x: number; y: number } | null>(null); - const [chartOption, setChartOption] = useState<WrStatusChartProps>({} as WrStatusChartProps) + const [chartOption, setChartOption] = useState<WrStatusChartProps>({} as WrStatusChartProps); + const svgContainerRef = useRef<HTMLDivElement>(null); const colorLegend = [ { label: '하수', color: '#FF6B6B', key: 'sewage' }, @@ -40,7 +41,8 @@ export default function WrStatusPage() { { label: '중수도', color: '#45B7D1', key: 'reclaimed' }, ]; const facilityTotalsColor = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FED16A']; - const defaultYear= '2023' + // const [defaultYear, setDefaultYear] = useState<string>(""); + // const defaultYear= '2023'; const handleMarkerClick = (regionId: string) => { @@ -151,11 +153,10 @@ export default function WrStatusPage() { useEffect(() => { - if(Common.selectSidoData&&Common.selectSidoData.length>1) - getDmStatusData(Common.selectSidoData); - },[Common.selectSidoData]) - - + if(Common.selectSidoData&&Common.selectSidoData.length>1) { + getDmStatusData(Common.selectSidoData); + } + },[Common.selectSidoData, getDmStatusData]) useEffect(() => { const loadSVG = async () => { diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index ea1e4b174..e410ad2a7 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -235,6 +235,7 @@ const Map = () => { <> <div className="bg-container"> <Gis + showZoomLevel={true} height='h-screen' treeData={treeData} isSearch={false} diff --git a/apps/dm/app/layout.tsx b/apps/dm/app/layout.tsx index 462537e09..7224c9dc2 100644 --- a/apps/dm/app/layout.tsx +++ b/apps/dm/app/layout.tsx @@ -1,5 +1,8 @@ "use client"; import "./globals.css"; +import { setCookie } from "../src/shared/lib"; + +setCookie("site-id-key", process.env.NEXT_PUBLIC_SITE_ID || "DM"); export default function RootLayout({ children, diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index b5b258839..2e52ac13d 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -50,8 +50,8 @@ export const sdmInfoFormSchema = z.object({ // 모든 수질 관련 필드 // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. const allFields: (keyof typeof data)[] = [ - "tn", "combResdch", "tph", - "trbt", "ph", "bod", "clIonQnt", + "tn", "combResdch", "tph", + "trbt", "ph", "bod", "clIonQnt", "smll", "cdtnv", "crmty", ]; @@ -104,38 +104,38 @@ export type SdmInfoFormData = z.infer<typeof sdmInfoFormSchema>; // 수요처 정보 export type DemandCompBizType = { - index : number | ''; - dmdInfoId : string | ''; - dmdInfoFk : string | ''; - bzmnNo : string | ''; - dmdCoNm : string | ''; - daddr : string | ''; - telno : string | ''; - rgtr : string | ''; - useUsgCd : string | ''; - ctpvCd : string | ''; - sggCd : string | ''; - dmdAplyPswd : string | ''; - wntUntprc : number | ''; - dmndTmng : Date | ''; - dmndQty : number | ''; - grssClfg : number | null //총대장균군수 - tn : number | null //총질소 - combResdch : number | null //결합잔류염소 - tph : number | null //총인 - trbt : number | null //탁도 - ph : string | ''; //수소이온농 - blgcOxygDmndQnt : number | null //생물학적산소요구량 - clIonQnt : number | null //염화물 - smll : string | ''; //냄새 - cdtnv : number | null; //전기전도도 - crmty : number | null //색도 - rmrk : string | ''; - reqDt : string | ''; - dmdInfoSn : number | ''; - status : string | ''; - splyCndtList:SupplyCandidate[]|[], - ruseUtztnFcltNm?:string|'' + index: number | ''; + dmdInfoId: string | ''; + dmdInfoFk: string | ''; + bzmnNo: string | ''; + dmdCoNm: string | ''; + daddr: string | ''; + telno: string | ''; + rgtr: string | ''; + useUsgCd: string | ''; + ctpvCd: string | ''; + sggCd: string | ''; + dmdAplyPswd: string | ''; + wntUntprc: number | ''; + dmndTmng: Date | ''; + dmndQty: number | ''; + grssClfg: number | null //총대장균군수 + tn: number | null //총질소 + combResdch: number | null //결합잔류염소 + tph: number | null //총인 + trbt: number | null //탁도 + ph: string | ''; //수소이온농 + blgcOxygDmndQnt: number | null //생물학적산소요구량 + clIonQnt: number | null //염화물 + smll: string | ''; //냄새 + cdtnv: number | null; //전기전도도 + crmty: number | null //색도 + rmrk: string | ''; + reqDt: string | ''; + dmdInfoSn: number | ''; + status: string | ''; + splyCndtList: SupplyCandidate[] | [], + ruseUtztnFcltNm?: string | '' } @@ -147,107 +147,107 @@ export type SupplyCandidate = { export type DemandMatchings = { - splyFcltFk : string | ''; - splyFcltFlg : string | ''; - pbnstRuseFcltBizFk : number | ''; - splyBgngDay : string | ''; - splyEndDay : string | ''; - splyCpct : number | ''; - splyFmYn : string | ''; - useUsgCd : string | ''; - dmdInfoSn : number | ''; - splyCndtList : string | ''; - mtchnCmptnYmd : string | ''; - rvwCmptnYmd : string | ''; - regDt : string | ''; - rgtr : string | ''; - mdfcnDt : string | ''; - mdfr : string | ''; + splyFcltFk: string | ''; + splyFcltFlg: string | ''; + pbnstRuseFcltBizFk: number | ''; + splyBgngDay: string | ''; + splyEndDay: string | ''; + splyCpct: number | ''; + splyFmYn: string | ''; + useUsgCd: string | ''; + dmdInfoSn: number | ''; + splyCndtList: string | ''; + mtchnCmptnYmd: string | ''; + rvwCmptnYmd: string | ''; + regDt: string | ''; + rgtr: string | ''; + mdfcnDt: string | ''; + mdfr: string | ''; } // 유사사례 export type SimilarCaseType = { - ruseBizNm : string | ''; - bizBgngDate : string | ''; - bizEndDate : string | ''; - splyFcltKnd : string | ''; - splyFcltNm : string | ''; - splyFcltCpct : string | ''; - dmdFcltNm : string | ''; - dmdFcltAddr : string | ''; - ruseFcltCpct : string | ''; - ruseFcltPplLen : string | ''; - ruseFcltUsg : string | ''; - ruseFcltSplyCpct : string | ''; - ruseFcltPrcsMsrc : string | ''; - grnteWqUsg : string | ''; - grnteWqGrssClfg : string | ''; - grnteWqCombResdch : string | ''; - grnteWqTrbt : string | ''; - grnteWqBod : string | ''; - grnteWqSmll : string | ''; - grnteWqCrmty : string | ''; - grnteWqTn : string | ''; - grnteWqTp : string | ''; - grnteWqPh : string | ''; - grnteWqClIonQnt : string | ''; - grnteWqCndctStat : string | ''; - grnteWqRmrk : string | ''; - reqBizReuseFcltCst : string | ''; - reqBizPplCst : string | ''; - reqBizArchCst : string | ''; - reqBizMchnCst : string | ''; - reqBizMtrngCst : string | ''; - reqBizGisCst : string | ''; - reqBizCctvCst : string | ''; - reqBizCmcnCst : string | ''; - reqBizWstCst : string | ''; - reqBizDesignCst : string | ''; - reqBizSpvsCst : string | ''; - reqBizSbsdCst : string | ''; - reqBizEtc : string | ''; - reqBizGrssCst : string | ''; - prcmPlanNlep : string | ''; - prcmPlanLcep : string | ''; - prcmPlanBrdnCst : string | ''; - prcmPlanEcqrRt : string | ''; - prcmPlanGrssCst : string | ''; - clclnNlep : string | ''; - clclnLcep : string | ''; - clclnBrdnCst : string | ''; - clclnEcqrRt : string | ''; - clclnGrssCst : string | ''; + ruseBizNm: string | ''; // 사업명 + bizBgngDate: string | ''; + bizEndDate: string | ''; + splyFcltKnd: string | ''; + splyFcltNm: string | ''; + splyFcltCpct: string | ''; + dmdFcltNm: string | ''; + dmdFcltAddr: string | ''; + ruseFcltCpct: string | ''; + ruseFcltPplLen: string | ''; + ruseFcltUsg: string | ''; + ruseFcltSplyCpct: string | ''; + ruseFcltPrcsMsrc: string | ''; + grnteWqUsg: string | ''; + grnteWqGrssClfg: string | ''; + grnteWqCombResdch: string | ''; + grnteWqTrbt: string | ''; + grnteWqBod: string | ''; + grnteWqSmll: string | ''; + grnteWqCrmty: string | ''; + grnteWqTn: string | ''; + grnteWqTp: string | ''; + grnteWqPh: string | ''; + grnteWqClIonQnt: string | ''; + grnteWqCndctStat: string | ''; + grnteWqRmrk: string | ''; + reqBizReuseFcltCst: string | ''; + reqBizPplCst: string | ''; + reqBizArchCst: string | ''; + reqBizMchnCst: string | ''; + reqBizMtrngCst: string | ''; + reqBizGisCst: string | ''; + reqBizCctvCst: string | ''; + reqBizCmcnCst: string | ''; + reqBizWstCst: string | ''; + reqBizDesignCst: string | ''; + reqBizSpvsCst: string | ''; + reqBizSbsdCst: string | ''; + reqBizEtc: string | ''; + reqBizGrssCst: string | ''; + prcmPlanNlep: string | ''; + prcmPlanLcep: string | ''; + prcmPlanBrdnCst: string | ''; + prcmPlanEcqrRt: string | ''; + prcmPlanGrssCst: string | ''; + clclnNlep: string | ''; + clclnLcep: string | ''; + clclnBrdnCst: string | ''; + clclnEcqrRt: string | ''; + clclnGrssCst: string | ''; } // 사업비 상세정보 export type MoneyDetailType = { - reqBizGrssCst : string | ''; - reqBizReuseFcltCst : string | ''; - reqBizPplCst : string | ''; - reqBizArchCst : string | ''; - reqBizMchnCst : string | ''; - reqBizMtrngCst : string | ''; - reqBizGisCst : string | ''; - reqBizCctvCst : string | ''; - reqBizCmcnCst : string | ''; - reqBizWstCst : string | ''; - reqBizDesignCst : string | ''; - reqBizSpvsCst : string | ''; - reqBizSbsdCst : string | ''; - reqBizEtc : string | ''; - + reqBizGrssCst: string | ''; + reqBizReuseFcltCst: string | ''; + reqBizPplCst: string | ''; + reqBizArchCst: string | ''; + reqBizMchnCst: string | ''; + reqBizMtrngCst: string | ''; + reqBizGisCst: string | ''; + reqBizCctvCst: string | ''; + reqBizCmcnCst: string | ''; + reqBizWstCst: string | ''; + reqBizDesignCst: string | ''; + reqBizSpvsCst: string | ''; + reqBizSbsdCst: string | ''; + reqBizEtc: string | ''; + /** 재원조달계획(백만원원) */ - prcmPlanGrssCst : string | ''; - prcmPlanNlep : string | ''; - prcmPlanLcep : string | ''; - prcmPlanBrdnCst : string | ''; - prcmPlanEcqrRt : string | ''; + prcmPlanGrssCst: string | ''; + prcmPlanNlep: string | ''; + prcmPlanLcep: string | ''; + prcmPlanBrdnCst: string | ''; + prcmPlanEcqrRt: string | ''; /** 사업비 정산(백만원) */ - clclnGrssCst : string | ''; - clclnNlep : string | ''; - clclnLcep : string | ''; - clclnBrdnCst : string | ''; - clclnEcqrRt : string | ''; + clclnGrssCst: string | ''; + clclnNlep: string | ''; + clclnLcep: string | ''; + clclnBrdnCst: string | ''; + clclnEcqrRt: string | ''; } // 규칙 타입 선언 export type DisableRulesType = Record<string, string[]>; @@ -255,144 +255,144 @@ export type DisableRulesType = Record<string, string[]>; * Store - 데이터 ====================================*/ // 수요처 정보 -const demandCompList : DemandCompType[] = [ +const demandCompList: DemandCompType[] = [ ] const disableRules: DisableRulesType = { - "청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'], - "화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'], - "세척": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], - "살수": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], - "조경": ["combResdch", "crmty",'tph','cdtnv'], - "농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], - "농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], - "하천": ["combResdch", "trbt", "crmty" ,'clIonQnt','cdtnv'], - '공업':[] - }; + "청소": ["tn", 'tph', "clIonQnt", 'cdtnv'], + "화장실용수": ["tn", 'tph', "clIonQnt", 'cdtnv'], + "세척": ["combResdch", "crmty", 'tph', 'tn', "clIonQnt", 'cdtnv'], + "살수": ["combResdch", "crmty", 'tph', 'tn', "clIonQnt", 'cdtnv'], + "조경": ["combResdch", "crmty", 'tph', 'cdtnv'], + "농업_간접용수": ["combResdch", "crmty", 'tn', 'tph', 'clIonQnt'], + "농업_직접용수": ["combResdch", "crmty", 'tn', 'tph', 'clIonQnt'], + "하천": ["combResdch", "trbt", "crmty", 'clIonQnt', 'cdtnv'], + '공업': [] +}; - const defaultRules: Record<string, object> = { - "청소": { - grssClfg: null, - combResdch:0.2, - trbt: 2, - bod: 5, - crmty: 20, - ph: "5.8~8.5", - smll:'불쾌하지않을것', - tn:null, - tph: null, - clIonQnt: null, - cdtnv: null, +const defaultRules: Record<string, object> = { + "청소": { + grssClfg: null, + combResdch: 0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, - }, - "화장실용수": { - grssClfg: null, - combResdch:0.2, - trbt: 2, - bod: 5, - crmty: 20, - smll:'불쾌하지않을것', - ph: "5.8~8.5", - tn:null, - tph: null, - clIonQnt: null, - cdtnv: null, - }, - "세척": { - grssClfg: null, - trbt: 2, - bod: 5, - ph: "5.8~8.5", - combResdch:null, - smll:'불쾌하지않을것', - crmty:null, - tn:null, - tph: null, - clIonQnt: null, - cdtnv: null, - }, - "살수": { - grssClfg: null, - trbt: 2, - bod: 5, - ph: "5.8~8.5", - smll:'불쾌하지않을것', - combResdch:null, - crmty:null, - tn:null, - tph: null, - clIonQnt: null, - cdtnv: null, - }, - "조경": { - grssClfg: null, - trbt: 2, - bod: 5, - ph: "5.8~8.5", - smll:'불쾌하지않을것', - clIonQnt:250, - combResdch:null, - crmty:null, - tn:null, - tph: null, - cdtnv: null, - }, - "농업_간접용수": { - grssClfg: null, - trbt: 5, - bod: 8, - ph: "5.8~8.5", - cdtnv:2000, - combResdch:null, - crmty:null, - tn:null, - tph: null, - clIonQnt: null, - }, - "농업_직접용수": { - grssClfg: null, - trbt: 2, - bod: 8, - smll:'불쾌하지않을것', - ph: "5.8~8.5", - cdtnv:700, - combResdch:null, - crmty:null, - tn:null, - tph: null, - clIonQnt: null, - }, - "하천": { - grssClfg: 1000, - bod: 5, - tn: 10, - tph: 0.5, - smll:'불쾌하지않을것', - ph: "5.8~8.5", - combResdch:null, - trbt:null, - crmty:null, - clIonQnt: null, - cdtnv:null - }, - '공업':{ - grssClfg: null, - bod: null, - tn: null, - tph: null, - ph: "~", - combResdch:null, - smll:'불쾌하지않을것', - trbt:null, - crmty:null, - clIonQnt: null, - cdtnv:null - } - }; + }, + "화장실용수": { + grssClfg: null, + combResdch: 0.2, + trbt: 2, + bod: 5, + crmty: 20, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "세척": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch: null, + smll: '불쾌하지않을것', + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "살수": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "조경": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + clIonQnt: 250, + combResdch: null, + crmty: null, + tn: null, + tph: null, + cdtnv: null, + }, + "농업_간접용수": { + grssClfg: null, + trbt: 5, + bod: 8, + ph: "5.8~8.5", + cdtnv: 2000, + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + }, + "농업_직접용수": { + grssClfg: null, + trbt: 2, + bod: 8, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + cdtnv: 700, + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + }, + "하천": { + grssClfg: 1000, + bod: 5, + tn: 10, + tph: 0.5, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + combResdch: null, + trbt: null, + crmty: null, + clIonQnt: null, + cdtnv: null + }, + '공업': { + grssClfg: null, + bod: null, + tn: null, + tph: null, + ph: "~", + combResdch: null, + smll: '불쾌하지않을것', + trbt: null, + crmty: null, + clIonQnt: null, + cdtnv: null + } +}; // 사용용도 -const useTypesData : SelectDataType[] = [ +const useTypesData: SelectDataType[] = [ { text: '청소', val: "청소" }, { text: "화장실용수", val: "화장실용수" }, { text: "세척", val: "세척" }, @@ -440,8 +440,8 @@ export const similarCaseModalHeader: TableUpperProps[] = [ ] // 유사사례 데이터 -const similarCaseList : SimilarCaseType[] = [ - +const similarCaseList: SimilarCaseType[] = [ + ] // 사업비 상세정보 모달 헤더 @@ -477,12 +477,12 @@ export const moneyDetailModalHeader: TableUpperProps[] = [ ] // 사업비 상세정보 -const moneyDetailList : MoneyDetailType[] = [ +const moneyDetailList: MoneyDetailType[] = [ ] // 수요정보 신청현황 헤더 export const upHeadSdmInfoStatus: TableUpperProps[] = [ - { id: "index", title: "No."}, + { id: "index", title: "No." }, { id: "ctpvCd", title: "시도", row: 0, rowSpan: 2 }, { id: "sggCd", title: "시군구", row: 0, rowSpan: 2 }, { id: "dmdCoNm", title: "회사명", row: 0, rowSpan: 2 }, @@ -501,7 +501,7 @@ export const upHeadSdmInfoStatus: TableUpperProps[] = [ const postSdmData = async (data: DemandCompType) => { try { await postFetch("/reuseFc/request", data); - sessionStorage.setItem("user", JSON.stringify(data) ); + sessionStorage.setItem("user", JSON.stringify(data)); toast.success("수요처 등록에 성공하였습니다."); return true; } catch (error) { @@ -526,16 +526,16 @@ const postSdmLoginData = async (data: DemandCompType) => { try { const res = await postFetch("/reuseFc/request/login", data); const jsonData = await res.json(); - + const status = jsonData.status; const loginData = jsonData.data; let loginSuccess = false; - if(status === 200 && loginData != null) { + if (status === 200 && loginData != null) { alert("수요처 로그인에 성공하였습니다."); sessionStorage.setItem("user", JSON.stringify(loginData)); loginSuccess = true; - } else if(status === 401) { - alert("패스워드가 틀렸습니다."); + } else if (status === 401) { + alert("패스워드가 일치하지 않습니다."); loginSuccess = false; } else { alert("등록된 수요처가 없습니다."); @@ -553,35 +553,35 @@ const getDemandCompList = async (searchText: string, pageCnt: number) => { try { const url = '/reuseFc/request/' + (searchText ? searchText : '') + '/' + pageCnt; const res = await getFetch(fetchType.WR, url); - return { - data: res?.data || [], - count: res?.count || 0 + return { + data: res?.data || [], + count: res?.count || 0 } } catch (error) { - console.error(error+' 수요처 정보를 불러오지 못했습니다.'); + console.error(error + ' 수요처 정보를 불러오지 못했습니다.'); return { data: [], count: 0 }; } } -const getSdmInfoStatusList = async (dmdInfoId : string) => { +const getSdmInfoStatusList = async (dmdInfoId: string) => { try { const url = '/reuseFc/request/' + dmdInfoId; const res = await getFetch(fetchType.WR, url); return res?.data || []; } catch (error) { - console.error(error+' 수요정보 신청현황을 불러오지 못했습니다.'); + console.error(error + ' 수요정보 신청현황을 불러오지 못했습니다.'); return []; } } // 유사사례 조회 -const fetchGetSimilarCaseList = async (part: string, value: number|string) => { +const fetchGetSimilarCaseList = async (part: string, value: number | string) => { try { const url = '/reuseFc/pubFcRuseCase/dm/' + part + '/' + (value ? value : 0); const res = await getFetch(fetchType.WR, url); return res?.data || []; } catch (error) { - console.error(error+' 유사사례 조회에 실패하였습니다.'); + console.error(error + ' 유사사례 조회에 실패하였습니다.'); return []; } } @@ -590,23 +590,24 @@ const fetchGetSimilarCaseList = async (part: string, value: number|string) => { * Store - expaort ====================================*/ export type SdmActions = { - initializeSdmData: () => void; - insertSdmData: (data: DemandCompType) => Promise<void>; - insertSdmInfoData: (data: DemandCompBizType) => Promise<void>; - getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>; - actionLogin: (data: DemandCompType) => Promise<void>; - getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>; - getSimilarCaseList: (part: string, value: number|string) => Promise<void>; + initializeSdmData: () => void; + insertSdmData: (data: DemandCompType) => Promise<void>; + insertSdmInfoData: (data: DemandCompBizType) => Promise<void>; + getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>; + actionLogin: (data: DemandCompType) => Promise<void>; + getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>; + getSimilarCaseList: (part: string, value: number | string) => Promise<void>; + actionLogout: () => Promise<void>; } -export type SdmType ={ - Sdm :{ +export type SdmType = { + Sdm: { isInit: boolean; useTypesData: SelectDataType[]; demandCompList: DemandCompType[]; upHeadSimilarCaseList: TableUpperProps[]; similarCaseList: SimilarCaseType[]; - upHeadMoneyDetailList : TableUpperProps[]; + upHeadMoneyDetailList: TableUpperProps[]; moneyDetailList: MoneyDetailType[]; // 수요정보 신청현황 @@ -615,28 +616,28 @@ export type SdmType ={ defaultRules: Record<string, object> disableRules: DisableRulesType; // 로그인관련 - successNextPage : boolean; + successNextPage: boolean; successLogin: boolean; } } export const SdmState: SdmType = { - Sdm:{ - isInit:false, + Sdm: { + isInit: false, useTypesData: useTypesData, demandCompList: demandCompList, upHeadSimilarCaseList: similarCaseModalHeader, similarCaseList: similarCaseList, - upHeadMoneyDetailList : moneyDetailModalHeader, + upHeadMoneyDetailList: moneyDetailModalHeader, moneyDetailList: moneyDetailList, // 수요정보 신청현황 sdmInfoStatusList: [], - defaultRules:defaultRules, - disableRules:disableRules, + defaultRules: defaultRules, + disableRules: disableRules, upHeadSdmInfoStatus: upHeadSdmInfoStatus, // 로그인관련 - successNextPage : false, + successNextPage: false, successLogin: false, } } @@ -648,13 +649,13 @@ export const SdmReducer = (set: any): SdmActions => { insertSdmData: async (data: DemandCompType) => { const preRes = await getSdmInfoStatusList(data.dmdInfoId); - if(preRes?.dmdInfoId) { + if (preRes?.dmdInfoId) { toast.error('이미 등록된 수요처입니다.'); return; } const res = await postSdmData(data); - if(res) { + if (res) { set((state: SdmType) => ({ Sdm: { ...state.Sdm, @@ -671,7 +672,7 @@ export const SdmReducer = (set: any): SdmActions => { // 수요처 정보 getDemandCompList: async (searchText: string, pageCnt: number) => { const res = await getDemandCompList(searchText, pageCnt); - if(res.data.length === 0) { + if (res.data.length === 0) { set((state: SdmType) => ({ Sdm: { ...state.Sdm, @@ -693,7 +694,7 @@ export const SdmReducer = (set: any): SdmActions => { getSdmInfoStatusList: async (dmdInfoId: string) => { const res = await getSdmInfoStatusList(dmdInfoId); - if(res.length === 0) { + if (res.length === 0) { set((state: SdmType) => ({ Sdm: { ...state.Sdm, @@ -704,13 +705,13 @@ export const SdmReducer = (set: any): SdmActions => { } //const sdmInfoStatusList : DemandCompBizType[] = []; - const sdmInfoStatusList : DemandCompBizType[]= res.demandInfo.map((el: DemandCompBizType, index: number) => { + const sdmInfoStatusList: DemandCompBizType[] = res.demandInfo.map((el: DemandCompBizType, index: number) => { const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; const ruseUtztnFcltNm = matchingInfo?.ruseUtztnFcltNm ? matchingInfo.ruseUtztnFcltNm : ''; - return( { + return ({ ...el, index: ++index, bzmnNo: res.bzmnNo, @@ -723,8 +724,8 @@ export const SdmReducer = (set: any): SdmActions => { sggCd: res.sggCd, useUsgCd: el.useUsgCd, dmndTmng: el.dmndTmng, - dmdInfoSn : el.dmdInfoSn, - status : status, + dmdInfoSn: el.dmdInfoSn, + status: status, splyCndtList: el.splyCndtList, ruseUtztnFcltNm: ruseUtztnFcltNm || '' }) @@ -749,25 +750,47 @@ export const SdmReducer = (set: any): SdmActions => { }, // 유사사례 조회 - getSimilarCaseList: async (part: string, value: number|string) => { + getSimilarCaseList: async (part: string, value: number | string) => { const res = await fetchGetSimilarCaseList(part, value); - if(res && res.length !== 0) { + const similarCaseList = res.map((el: SimilarCaseType) => { + const maskedBizNm = el.ruseBizNm + ? el.ruseBizNm.replace(/^(\s*)^\S+/, "***") // 첫 단어(공백 전까지) 치환 + : el.ruseBizNm; + + return { + ...el, + ruseBizNm: maskedBizNm, + }; + }); + console.log(similarCaseList); + + if (similarCaseList && similarCaseList.length !== 0) { set((state: SdmType) => ({ - Sdm: { + Sdm: { ...state.Sdm, - similarCaseList: res, + similarCaseList: similarCaseList, } })) return; - }else{ + } else { set((state: SdmType) => ({ - Sdm: { + Sdm: { ...state.Sdm, similarCaseList: [], } })) return; } + }, + + actionLogout: async () => { + sessionStorage.removeItem("user"); + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + successLogin: false, + } + })) } } } diff --git a/apps/dm/app/store/wrStatus/index.tsx b/apps/dm/app/store/wrStatus/index.tsx index ec8911f51..d3cc61cc4 100644 --- a/apps/dm/app/store/wrStatus/index.tsx +++ b/apps/dm/app/store/wrStatus/index.tsx @@ -8,11 +8,12 @@ import type { SelectDataType } from "../common"; import { makeRegion } from "@/app/common/comFunction"; export type DmStatusActions = { - getDmStatusData(cityCd: SelectDataType[]): void; + getDmStatusData(cityCd: SelectDataType[]): void; // 데이터의 연도 반환 }; export type DmStatusType = { dmStatus: { + year : string; statusData: DmStatusDataType[]; facilityTotals: string[]; chartData: DmStatusChartType[]; @@ -21,6 +22,7 @@ export type DmStatusType = { export const dmStatusState: DmStatusType = { dmStatus: { + year : '', statusData: [], facilityTotals: [], chartData: [], @@ -32,6 +34,7 @@ export const DmStatusReducer = (set: any): DmStatusActions => { return { getDmStatusData: async (cityCd: SelectDataType[]) => { const data = await getFetch(fetchType.FO, "/dashBoard/daemin"); + const year = data.data[0]?.year; // API 응답에서 year 값을 할당합니다. console.log('cityCd ', cityCd) const dataSum = data.data.reduce( (acc: DmStatusDataType, item: DmStatusDataType) => { @@ -73,7 +76,7 @@ export const DmStatusReducer = (set: any): DmStatusActions => { ); data.data.unshift(dataSum); - console.log("dataSum ", dataSum); + //console.log("dataSum ", dataSum); const facilityTotals = [ "하수재이용시설" + "(총" + dataSum?.sewageCnt + "개소)", "빗물재이용시설" + "(총" + dataSum?.rainwaterCnt + "개소)", @@ -82,27 +85,25 @@ export const DmStatusReducer = (set: any): DmStatusActions => { const boardData: DmStatusBoardType[] = data.data.map( (item: DmStatusDataType) => { - const regionText = cityCd.find((city) => item.region === city.val); + const regionText = item.sidoNm; //cityCd.find((city) => item.region === city.val); const sewage = item.sewage; const rainwater = item.rainwater; const reclaimed = item.reclaimed; const sewageCnt = item.sewageCnt; const rainwaterCnt = item.rainwaterCnt; const reclaimedCnt = item.reclaimedCnt; - console.log("regionText ", regionText); - if (regionText && regionText.text) { + return { - region: regionText ? regionText.text : item.region, - sewage: sewageCnt + "(" + sewage + ")", - rainwater: rainwaterCnt + "(" + rainwater + ")", - reclaimed: reclaimedCnt + "(" + reclaimed + ")", + region: regionText ? regionText : item.region, + sewage: sewageCnt + "(" + sewage.toLocaleString() + ")", + rainwater: rainwaterCnt + "(" + rainwater.toLocaleString() + ")", + reclaimed: reclaimedCnt + "(" + reclaimed.toLocaleString() + ")", }; - } } ).filter((item): item is DmStatusBoardType => item !== undefined); - console.log("boardData ", boardData); - + //console.log("boardData ", boardData); + const chartData = data.data.map((item) => ({ label: makeRegion( cityCd.find((city) => item.region === city.val)?.text ?? "" @@ -116,11 +117,13 @@ export const DmStatusReducer = (set: any): DmStatusActions => { ...state, dmStatus: { ...state.dmStatus, + year : year, statusData: boardData, chartData: chartData, facilityTotals: facilityTotals, }, })); + }, }; }; diff --git a/apps/dm/app/store/wrStatus/type.ts b/apps/dm/app/store/wrStatus/type.ts index 513a5fb01..2cf1b36f0 100644 --- a/apps/dm/app/store/wrStatus/type.ts +++ b/apps/dm/app/store/wrStatus/type.ts @@ -1,14 +1,16 @@ export type DmStatusDataType = { - region: string; + sidoNm: string; // 시도명 + region: string; // 시도코드 sewage: string; rainwater: string; reclaimed: string; - sewageCnt:string - rainwaterCnt:string - reclaimedCnt:string + sewageCnt:string; + rainwaterCnt:string; + reclaimedCnt:string; }; export type DmStatusBoardType = { + sidoNm: string; // 시도명 region: string; sewage: string; rainwater: string; diff --git a/apps/dm/components/ui/api/index.ts b/apps/dm/components/ui/api/index.ts index 7ba66c4ed..abb0c2c73 100644 --- a/apps/dm/components/ui/api/index.ts +++ b/apps/dm/components/ui/api/index.ts @@ -1,23 +1,33 @@ //8880 import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; -import { DEV_URL_FO, DEV_URL_WR } from "../config"; import { getCookie } from "@common/components/lib/cookie"; + const tokenId = getCookie(AUTH_USER_ID); const Authorization = getCookie(CLAIM_NAME); export const fetchType = { COMMON: "common", WR: "wr", - FO:'fo' + FO: "fo", + SN: "sn" }; +const API_VERSION = "/api/v1" +const servers = { + common: "", + local: "", + fo: "/operation-service" + API_VERSION, + wr: "/reuse-service" + API_VERSION, + sn: "/sewerage-service" + API_VERSION, +} + interface RequestParams { [key: string]: unknown; } export const getFetch = async (type: string, url: string) => { - const baseUrl = type === fetchType.FO ? DEV_URL_FO : DEV_URL_WR; + const baseUrl = servers[type]; const response = await fetch(baseUrl + url, { method: "GET", headers: { @@ -35,7 +45,8 @@ export const getFetch = async (type: string, url: string) => { }; export const postFetch = async (url: string, params: RequestParams) => { - const response = await fetch(DEV_URL_WR + url, { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { method: "POST", headers: { "Content-Type": "application/json", @@ -52,7 +63,8 @@ export const postFetch = async (url: string, params: RequestParams) => { }; export const putFetch = async (url: string, params: RequestParams) => { - const response = await fetch(DEV_URL_WR + url, { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { method: "PUT", headers: { "Content-Type": "application/json", @@ -69,7 +81,8 @@ export const putFetch = async (url: string, params: RequestParams) => { }; export const deleteFetch = async (url: string, params: RequestParams) => { - const response = await fetch(DEV_URL_WR + url, { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { method: "DELETE", headers: { "Content-Type": "application/json", diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 2ba965a6e..62b214f04 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -34,7 +34,7 @@ export interface SdmInfoRegFormProps { type FieldComponent = ( field: ControllerRenderProps<SdmInfoFormData, keyof SdmInfoFormData> -) => JSX.Element; +) => React.ReactElement<{ disabled?: boolean }>; export function SdmInfoRegForm({ form, @@ -950,7 +950,7 @@ export function SdmInfoRegForm({ body={ <ModalSimilarPage sply={sply} usageCd={usageCd} money={money} /> } - bodyClass="sm:max-w-[100vw] w-[1400px] h-[900px]" + bodyClass="sm:max-w-[100vw] w-[1400px] h-[100dvh] max-h-[900px] overflow-y-auto overflow-x-hidden" open={isSimilarCase} onOpenChange={(open) => !open && setIsSimilarCase(false)} noHeaderYn="Y" diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 7617b4b31..7ec417c71 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -4,7 +4,7 @@ import type { DemandCompType } from "@common/business_components/ui/sdmInsertFor import { Button, Input } from "@common/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { sha512 } from 'js-sha512'; -import { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form"; import toast from "react-hot-toast"; import * as z from "zod"; @@ -47,7 +47,7 @@ export function SdmLoginForm() { }), [isBznmTotNoDisabled]); type SdmLoginForm = z.infer<typeof sdmLoginFormSchema>; - type FieldComponent = (field: ControllerRenderProps<SdmLoginForm, keyof SdmLoginForm>) => JSX.Element; + type FieldComponent = (field: ControllerRenderProps<SdmLoginForm, keyof SdmLoginForm>) => React.ReactElement; const formSchema = sdmLoginFormSchema; diff --git a/apps/dm/middleware.ts b/apps/dm/middleware.ts index 0b2b21fbb..44d4cb390 100644 --- a/apps/dm/middleware.ts +++ b/apps/dm/middleware.ts @@ -1,13 +1,13 @@ import { NextRequest, NextResponse } from "next/server"; import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config"; -import { SERVER_API_FO_URL, SERVER_API_WR_URL } from "./src/shared/config/env"; +// import { SERVER_API_FO_URL, SERVER_API_WR_URL } from "./src/shared/config/env"; // const CLAIM_NAME = "Authorization"; // const AUTH_USER_ID = "token-id"; const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_URL; const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; -const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; +const GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL || ""; const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || ""; const AdressKey = process.env.NEXT_PRIVATE_WR_ADDRESS_KEY || ""; @@ -112,7 +112,7 @@ export function middleware(req: NextRequest) { const url = req.nextUrl.clone(); url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); queryParams = url.searchParams.toString(); - newUrl = `${SEWERAGE_API_URL}/api/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; + newUrl = `${GATEWAY_URL}/api/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; } console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); @@ -121,24 +121,17 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/reuse-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SERVER_API_WR_URL}${pathname.replace("/reuse-service", "")}${queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); - } - - if (pathname.startsWith("/operation-service")) { + } else if (pathname.startsWith("/operation-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SERVER_API_FO_URL}${pathname.replace("/operation-service", "")}${queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); - } - - if (pathname.startsWith("/sewerage-service")) { + } else if (pathname.startsWith("/sewerage-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service", "")}${queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; console.log(`[Middleware] Rewriting from '/sewerage-service' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } diff --git a/apps/dm/next.config.ts b/apps/dm/next.config.ts index bc21e16a4..5e1533ec9 100644 --- a/apps/dm/next.config.ts +++ b/apps/dm/next.config.ts @@ -5,6 +5,9 @@ const subDomain = const nextConfig: NextConfig = { output: "standalone", + experimental: { + proxyTimeout: 600000, + }, async rewrites() { return { beforeFiles: [ diff --git a/apps/dm/package.json b/apps/dm/package.json index 07ddec950..71d504f50 100644 --- a/apps/dm/package.json +++ b/apps/dm/package.json @@ -18,7 +18,7 @@ "js-cookie": "^3.0.5", "js-sha512": "^0.9.0", "lucide-react": "^0.474.0", - "next": "15.1.7", + "next": "15.3.8", "react-hook-form": "^7.54.2", "react-hot-toast": "^2.5.2", "tailwind-merge": "^3.0.1", diff --git a/apps/dm/public/images/map/layericon/RDL_TSCT_PS500.png b/apps/dm/public/images/map/layericon/RDL_TSCT_PS500.png new file mode 100644 index 000000000..b82e2ff89 Binary files /dev/null and b/apps/dm/public/images/map/layericon/RDL_TSCT_PS500.png differ diff --git a/apps/dm_new/.env.local b/apps/dm_new/.env.local new file mode 100644 index 000000000..58001643c --- /dev/null +++ b/apps/dm_new/.env.local @@ -0,0 +1,26 @@ +NEXT_PUBLIC_SITE_ID = DN +NEXT_PUBLIC_PORT = 3015 +NEXT_PUBLIC_CLAIM_NAME = Authorization +NEXT_PUBLIC_AUTH_USER_ID = token-id +NEXT_PUBLIC_ACCESS_TOKEN = access-token +NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +NEXT_PUBLIC_PROXY_HOST = http://localhost:3005 +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +# NEXT_PUBLIC_FO_SERVER_API_URL = http://192.168.1.222:8883/operation-service/api/v1 +NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver +NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload +NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr +NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} +NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 + + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm_new/.env.mindone b/apps/dm_new/.env.mindone new file mode 100644 index 000000000..9a5005d2b --- /dev/null +++ b/apps/dm_new/.env.mindone @@ -0,0 +1,26 @@ +NEXT_PUBLIC_SITE_ID = DN +NEXT_PUBLIC_PORT = 11315 +NEXT_PUBLIC_CLAIM_NAME = Authorization +NEXT_PUBLIC_AUTH_USER_ID = token-id +NEXT_PUBLIC_ACCESS_TOKEN = access-token +NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11315 +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +#NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver +NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload +NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr +NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} +NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API +NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API +# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 + + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm_new/.env.production b/apps/dm_new/.env.production new file mode 100644 index 000000000..b413d1312 --- /dev/null +++ b/apps/dm_new/.env.production @@ -0,0 +1,24 @@ +# 공단 운영 +NEXT_PUBLIC_SITE_ID = DN +NEXT_PUBLIC_PORT = 3000 +NEXT_PUBLIC_CLAIM_NAME = Authorization +NEXT_PUBLIC_AUTH_USER_ID = token-id +NEXT_PUBLIC_ACCESS_TOKEN = access-token +NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +#NEXT_PUBLIC_PROXY_HOST = https://devreuse.hasuon.or.kr +#NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login +NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 +NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 +NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 +NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver +NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl +NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload +NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service +NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact +NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} +NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 + + +NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do +NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= diff --git a/apps/dm_new/app/(landing)/reuse/_components/CaseTabsCarousel.tsx b/apps/dm_new/app/(landing)/reuse/_components/CaseTabsCarousel.tsx new file mode 100644 index 000000000..ece8f0848 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/CaseTabsCarousel.tsx @@ -0,0 +1,578 @@ +"use client"; + +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import Image from "next/image"; + +/** + * CaseTabsCarousel.tsx (FINAL OPTIMIZED) + * - 탭별 데이터 + * - 카드 perPage(1/2/3) 반응형 + * - 카드 perPage개 = 1그룹, 좌/우 클릭 시 1그룹 단위 이동 + * - 3그룹 가상화(prev/cur/next)로 DOM 최소화 + * - px 기반 트랙 이동 + "측정 박스 분리"로 좌/우 비침 방지 + * - stale state 제거(방향/인덱스 ref 관리)로 슬라이드 안정성 강화 + * - BUGFIX 1) onTransitionEnd 버블링 방지 (target===currentTarget, transform만 처리) + * - BUGFIX 2) 슬라이드 중(busy) clipWidth 변화로 트랙 스냅(-W) 덮어쓰지 않게 분리 + */ + +type TabKey = "rain" | "reuse" | "sewage" | "global"; + +type CaseItem = { + id: string; + title: string; + desc: string; + imageSrc: string; + href?: string; +}; + +const TABS: { key: TabKey; label: string }[] = [ + { key: "rain", label: "빗물 재이용 사례" }, + { key: "reuse", label: "중수 재이용 사례" }, + { key: "sewage", label: "하수 재이용 사례" }, + { key: "global", label: "해외 재이용 사례" }, +]; + +// 실제 데이터로 교체해서 쓰세요(구조 유지) +const MOCK: Record<TabKey, CaseItem[]> = { + rain: [ + { + id: "rain-0", + title: "경기도 수원시 빗물이용과 중수도", + desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…", + imageSrc: "/images/landing/rain_01.png", + href: "/reuse/business/rain/suwon", + }, + // { + // id: "rain-2", + // title: "경기도 수원시 빗물이용과 중수도", + // desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…", + // imageSrc: "/images/landing/rain_01.png", + // href: "#", + // }, + ], + reuse: Array.from({ length: 6 }).map((_, i) => ({ + id: `reuse-${i}`, + title: `중수 재이용 사례 ${i + 1}`, + desc: "설명 문구가 들어갑니다. (최대 2~3줄) …", + imageSrc: "/images/landing/sample_case.png", + href: "#", + })), + sewage: [ + { + id: "sewage-1", + title: "경기도 파주시 하수 재이용", + desc: "운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에...", + imageSrc: "/images/landing/sewage_01.png", + href: "/reuse/business/sewage/paju", + }, + { + id: "sewage-2", + title: "충남보령 하수재이용", + desc: "보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업...", + imageSrc: "/images/landing/sewage_02.png", + href: "/reuse/business/sewage/bolyeong", + }, + { + id: "sewage-3", + title: "여수시 하수처리수 재이용", + desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로...", + imageSrc: "/images/landing/sewage_03.png", + href: "/reuse/business/sewage/yeosu", + }, + { + id: "sewage-4", + title: "청주시 하수 재이용 시설", + desc: "청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO)...", + imageSrc: "/images/landing/sewage_04.png", + href: "/reuse/business/sewage/cheongju", + }, + { + id: "sewage-5", + title: "수원시 황구지천 하천유지용수", + desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로...", + imageSrc: "/images/landing/sewage_05.png", + href: "/reuse/business/sewage/hwanggu", + }, + { + id: "sewage-6", + title: "구리시 도로자동청소시스템", + desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용...", + imageSrc: "/images/landing/sewage_06.png", + href: "/reuse/business/sewage/guri", + }, + // ...Array.from({ length: 6 }).map((_, i) => ({ + // id: `sewage-x-${i}`, + // title: `하수 재이용 사례 ${i + 4}`, + // desc: "설명 문구가 들어갑니다. (최대 2~3줄) …", + // imageSrc: "/images/landing/sample_case.png", + // href: "#", + // })), + ], + global: [ + { + id: "global-1", + title: "해외 재이용 사례 개요", + desc: "2024년 하수도 통계 기준 15.6%이며, 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 17%, 우리나라의 하수 처리 재이용률은 15% 수준...", + imageSrc: "/images/landing/overview_01.png", + href: "reuse/business/globalCase/overview", + }, + { + id: "global-2", + title: "[대만] 국가 주도의 공업용수 공급", + desc: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑), 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능...", + imageSrc: "/images/landing/taiwan_01.png", + href: "/reuse/business/globalCase/taiwan", + }, + { + id: "global-3", + title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급", + desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망, 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급...", + imageSrc: "/images/landing/australia_01.png", + href: "/reuse/business/globalCase/australia", + }, + { + id: "global-4", + title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화", + desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설, NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용...", + imageSrc: "/images/landing/singapore_01.png", + href: "/reuse/business/globalCase/singapore", + }, + { + id: "global-5", + title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도", + desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행...", + imageSrc: "/images/landing/eu_01.png", + href: "/reuse/business/globalCase/eu", + }, + ], +}; + +/* ---------------- utils ---------------- */ +function chunk<T>(arr: T[], size: number) { + const res: T[][] = []; + if (size <= 0) return res; + for (let i = 0; i < arr.length; i += size) res.push(arr.slice(i, i + size)); + return res; +} +function mod(n: number, m: number) { + return ((n % m) + m) % m; +} +function usePrefersReducedMotion() { + const [reduced, setReduced] = useState(false); + useEffect(() => { + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + const onChange = () => setReduced(mq.matches); + onChange(); + mq.addEventListener?.("change", onChange); + return () => mq.removeEventListener?.("change", onChange); + }, []); + return reduced; +} + +/** + * wrapRef: perPage 계산용 + * clipRef: "패딩 없는 측정 박스" 폭(px) 계산용 + */ +function useCarouselMeasure() { + const wrapRef = useRef<HTMLDivElement | null>(null); + const clipRef = useRef<HTMLDivElement | null>(null); + + const [perPage, setPerPage] = useState(3); + const [clipWidth, setClipWidth] = useState(0); + + // perPage: container width로 반응형 결정 (rAF로 디바운스) + useEffect(() => { + const el = wrapRef.current; + if (!el) return; + + let raf = 0; + + const calc = () => { + const w = el.clientWidth; + if (w < 640) setPerPage(1); + else if (w < 1024) setPerPage(2); + else setPerPage(3); + }; + + const ro = new ResizeObserver(() => { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(calc); + }); + + calc(); + ro.observe(el); + return () => { + cancelAnimationFrame(raf); + ro.disconnect(); + }; + }, []); + + // clipWidth: 패딩 없는 박스에서 width를 정확히 측정 (sub-pixel 반올림) + useEffect(() => { + const el = clipRef.current; + if (!el) return; + + let raf = 0; + + const calc = () => { + const w = Math.round(el.getBoundingClientRect().width); + setClipWidth(w); + }; + + const ro = new ResizeObserver(() => { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(calc); + }); + + calc(); + ro.observe(el); + return () => { + cancelAnimationFrame(raf); + ro.disconnect(); + }; + }, []); + + return { wrapRef, clipRef, perPage, clipWidth }; +} + +/* ---------------- component ---------------- */ +export default function CaseTabsCarousel() { + const [active, setActive] = useState<TabKey>("rain"); + + // 탭별 마지막 그룹 인덱스 기억 + const tabIndexRef = useRef<Record<TabKey, number>>({ + rain: 0, + reuse: 0, + sewage: 0, + global: 0, + }); + + const reducedMotion = usePrefersReducedMotion(); + const { wrapRef, clipRef, perPage, clipWidth } = useCarouselMeasure(); + + const items = useMemo(() => MOCK[active] ?? [], [active]); + const groups = useMemo(() => chunk(items, perPage), [items, perPage]); + const groupCount = groups.length; + + const [cur, setCur] = useState(0); + const curRef = useRef(0); + + // px 기반 트랙 + const [trackPx, setTrackPx] = useState(0); + const [anim, setAnim] = useState(false); + const [busy, setBusy] = useState(false); + + // 방향 ref (onTransitionEnd에서 trackPx 비교하지 않음) + const dirRef = useRef<-1 | 1 | 0>(0); + + // cur 최신값 유지 + useEffect(() => { + curRef.current = cur; + }, [cur]); + + /** + * 리셋 로직 분리: + * - 탭/perPage/groupCount 바뀔 때만 "인덱스/상태" 리셋 + * - clipWidth 변화는 별도 effect에서 "busy 아닐 때만" 중앙(-W)로 배치 + */ + useLayoutEffect(() => { + const saved = tabIndexRef.current[active] ?? 0; + const nextCur = groupCount ? mod(saved, groupCount) : 0; + + dirRef.current = 0; + setCur(nextCur); + setBusy(false); + setAnim(false); + }, [active, perPage, groupCount]); + + // clipWidth 변화로 트랙 위치를 잡되, 슬라이드 중(busy)엔 덮어쓰지 않음 + useLayoutEffect(() => { + if (busy) return; + if (clipWidth > 0) setTrackPx(-clipWidth); + else setTrackPx(0); + }, [clipWidth, busy]); + + // 3그룹 가상화 + const viewGroups = useMemo(() => { + if (!groupCount) return [[], [], []] as CaseItem[][]; + return [ + groups[mod(cur - 1, groupCount)] ?? [], + groups[mod(cur, groupCount)] ?? [], + groups[mod(cur + 1, groupCount)] ?? [], + ]; + }, [groups, cur, groupCount]); + + const canSlide = groupCount > 1 && clipWidth > 0; + + const go = useCallback( + (dir: -1 | 1) => { + if (!canSlide) return; + if (busy) return; + + dirRef.current = dir; + + setBusy(true); + setAnim(!reducedMotion); + + // 중앙(-W) 기준: prev=0, next=-2W + setTrackPx(dir === 1 ? -2 * clipWidth : 0); + + // reduced motion이면 즉시 확정 + if (reducedMotion) { + const nextCur = mod(curRef.current + dir, groupCount); + tabIndexRef.current[active] = nextCur; + + setCur(nextCur); + setAnim(false); + setTrackPx(-clipWidth); + setBusy(false); + dirRef.current = 0; + } + }, + [active, busy, canSlide, clipWidth, groupCount, reducedMotion] + ); + + // BUGFIX 1) 자식 transitionend 버블링 방지 + transform만 처리 + const onTransitionEnd = useCallback( + (e: React.TransitionEvent<HTMLDivElement>) => { + // 자식 transitionend 버블링 방지 + if (e.target !== e.currentTarget) return; + if (e.propertyName !== "transform") return; + + if (!busy || reducedMotion) return; + + const dir = dirRef.current; + if (dir !== 1 && dir !== -1) return; + + const nextCur = mod(curRef.current + dir, groupCount); + tabIndexRef.current[active] = nextCur; + + // 1) 먼저 transition을 끄고(중요) + setAnim(false); + + // 2) cur를 회전 + 트랙을 즉시 중앙(-W)로 스냅 + // (이 순간은 "무조건 애니메이션 없이" 처리되어야 함) + setCur(nextCur); + setTrackPx(-clipWidth); + + // 3) 다음 프레임(2번 rAF)에서 다시 애니메이션 켜고 busy 해제 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setAnim(!reducedMotion); + setBusy(false); + dirRef.current = 0; + }); + }); + }, + [active, busy, clipWidth, groupCount, reducedMotion] +); + + const goDot = useCallback( + (idx: number) => { + if (!groupCount) return; + if (busy) return; + + const target = mod(idx, groupCount); + tabIndexRef.current[active] = target; + + dirRef.current = 0; + setCur(target); + setAnim(false); + if (clipWidth > 0) setTrackPx(-clipWidth); + }, + [active, busy, clipWidth, groupCount] + ); + + const trackStyle: React.CSSProperties = useMemo( + () => ({ + transform: `translate3d(${trackPx}px, 0, 0)`, + transition: anim ? "transform 700ms cubic-bezier(.22,.61,.36,1)" : "none", + willChange: busy ? "transform" : undefined, + }), + [trackPx, anim, busy] + ); + + return ( + <section className="w-full"> + {/* 탭 바 */} + <div className="mx-auto max-w-[1520px] pt-10"> + <div className=""> + <div className="grid grid-cols-4"> + {TABS.map((t) => { + const isActive = t.key === active; + + return ( + <button + key={t.key} + type="button" + onClick={() => setActive(t.key)} + className={[ + "relative h-[74px] w-full text-base font-semibold", + "transition-colors", + // 기본(비활성): 검정 배경 + 회색 텍스트 + !isActive ? "text-[#767676] hover:text-black border-b-2 border-sky-400" : "", + // 활성: 흰 배경 + 진한 텍스트 + 하늘색 테두리(위/좌/우) + 아래는 열어둠 + isActive + ? [ + "bg-[#eaf4f4] text-black", + "border-t-2 border-l-2 border-r-2 border-sky-400", + "border-b-0", // 아래는 컨텐츠와 이어지도록 열어둠 + "z-10", // 테두리가 위로 올라오게 + ].join(" ") + : "", + ].join(" ")} + > + {t.label} + + {/* 활성 탭 하단에 얇은 라인이 필요하면(이미지처럼) */} + {isActive && ( + <span className="pointer-events-none absolute left-0 right-0 bottom-[-1px] h-[2px] bg-[#eaf4f4]" /> + )} + </button> + ); + })} + </div> + </div> + </div> + + {/* 슬라이더 */} + <div ref={wrapRef} className="mx-auto max-w-[1520px] px-6 pb-5 pt-10 bg-[#eaf4f4]"> + <div className="relative"> + {/* 좌우 버튼 */} + <button + type="button" + onClick={() => go(-1)} + disabled={!canSlide || busy} + className={[ + "absolute left-0 top-1/2 z-10 -translate-y-1/2", + "w-[80px] h-[80px] rounded-full bg-white shadow-[0_10px_25px_rgba(0,0,0,0.12)]", + "grid place-items-center border-2 border-[#C2CAD2]", + !canSlide || busy + ? "opacity-40 cursor-not-allowed" + : "hover:brightness-95", + ].join(" ")} + aria-label="이전" + > + <Image + src="/images/landing/icon_left_arrow.png" + alt="이전" + width={24} + height={24} + className="w-6 h-6 object-contain" + /> + </button> + + <button + type="button" + onClick={() => go(1)} + disabled={!canSlide || busy} + className={[ + "absolute right-0 top-1/2 z-10 -translate-y-1/2", + "w-[80px] h-[80px] rounded-full bg-white shadow-[0_10px_25px_rgba(0,0,0,0.12)]", + "grid place-items-center border-2 border-[#C2CAD2]", + !canSlide || busy + ? "opacity-40 cursor-not-allowed" + : "hover:brightness-95", + ].join(" ")} + aria-label="다음" + > + <Image + src="/images/landing/icon_right_arrow.png" + alt="다음" + width={24} + height={24} + className="w-6 h-6 object-contain" + /> + </button> + + {/* 중요: clip(가림)과 measure(측정) 분리 */} + <div className="overflow-hidden [clip-path:inset(0)] [contain:paint]"> + <div className="w-[1250px] overflow-hidden m-auto"> + {/* 측정은 패딩 없는 박스에서 */} + <div ref={clipRef} className="w-full"> + {/* track */} + <div className="flex" style={trackStyle} onTransitionEnd={onTransitionEnd}> + {viewGroups.map((group, gi) => ( + <div + key={`pane-${gi}`} + className="shrink-0" + style={{ width: clipWidth || "100%" }} + > + {/* 그룹 내부: perPage 컬럼 */} + <div + className="grid gap-7" + style={{ + gridTemplateColumns: `repeat(${perPage}, minmax(0, 1fr))`, + }} + > + {(group ?? []).map((c) => ( + <article + key={c.id} + className="overflow-hidden rounded-[22px] border-2 border-sky-400 bg-white" + > + <div className="h-[241px] w-full bg-slate-100 border-b border-b-blue-300"> + <Image + src={c.imageSrc} + alt={c.title} + width={640} + height={241} + className="h-full w-full object-cover" + style={{ width: "100%", height: "100%" }} + loading="lazy" + /> + </div> + + <div className="p-8"> + <h3 className="h-[77px] text-center text-[28px] font-extrabold leading-snug tracking-tight"> + {c.title} + </h3> + <p className="mt-4 line-clamp-3 text-[16px] leading-7 text-slate-600"> + {c.desc} + </p> + + <a + href={c.href ?? "#"} + className="mt-8 inline-flex h-[56px] w-full items-center justify-center gap-3 rounded-md bg-[#02A0D6] text-[18px] font-bold text-white transition hover:brightness-110" + > + 자세히 보기 <span aria-hidden>→</span> + </a> + </div> + </article> + ))} + </div> + </div> + ))} + </div> + </div> + </div> + </div> + + {/* 도트 */} + <div className="mt-8 flex items-center justify-center gap-3"> + {Array.from({ length: Math.max(groupCount, 1) }).map((_, i) => ( + <button + key={`dot-${i}`} + type="button" + onClick={() => goDot(i)} + className={[ + "h-[10px] w-[10px] rounded-full transition", + i === cur + ? "bg-black opacity-100" + : "bg-black opacity-30 hover:opacity-60", + ].join(" ")} + aria-label={`${i + 1}번 그룹`} + /> + ))} + </div> + </div> + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/ImageSlider.tsx b/apps/dm_new/app/(landing)/reuse/_components/ImageSlider.tsx new file mode 100644 index 000000000..41d8987b8 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/ImageSlider.tsx @@ -0,0 +1,175 @@ +// apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx +"use client"; + +import Image from "next/image"; +import { useEffect, useMemo, useRef, useState } from "react"; + +type Slide = { src: string; alt?: string }; + +type Props = { + slides: Slide[]; + className?: string; + maxWidthClassName?: string; // 예: "max-w-[760px]" +}; + +export default function CaseImageSlider({ + slides, + className = "", + maxWidthClassName = "max-w-[760px]", +}: Props) { + const safeSlides = useMemo(() => slides.filter(Boolean), [slides]); + const [idx, setIdx] = useState(0); + const [dragging, setDragging] = useState(false); + + const startX = useRef(0); + const deltaX = useRef(0); + + const total = safeSlides.length; + const canSlide = total > 1; + + const go = (dir: number) => { + if (!canSlide) return; + setIdx((prev) => { + const next = prev + dir; + if (next < 0) return total - 1; + if (next >= total) return 0; + return next; + }); + }; + + // ← → 키보드 지원(선택) + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (!canSlide) return; + if (e.key === "ArrowLeft") go(-1); + if (e.key === "ArrowRight") go(1); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [canSlide, total]); + + // 드래그(마우스/터치)로 넘기기 + const onPointerDown = (e: React.PointerEvent) => { + if (!canSlide) return; + setDragging(true); + startX.current = e.clientX; + deltaX.current = 0; + (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); + }; + + const onPointerMove = (e: React.PointerEvent) => { + if (!dragging) return; + deltaX.current = e.clientX - startX.current; + }; + + const onPointerUp = () => { + if (!dragging) return; + setDragging(false); + + const threshold = 60; // 넘김 감도 + if (deltaX.current > threshold) go(-1); + else if (deltaX.current < -threshold) go(1); + + deltaX.current = 0; + }; + + if (!total) return null; + + return ( + <section className={className}> + <div className={`mx-auto ${maxWidthClassName}`}> + <div className="relative"> + {/* 좌측 버튼 */} + <button + type="button" + onClick={() => go(-1)} + className="absolute left-[-60px] top-1/2 -translate-y-1/2 z-10 + w-[80px] h-[80px] rounded-full border border-black/20 bg-white + grid place-items-center shadow-sm hover:bg-black/[0.03]" + > + <Image + src="/images/landing/icon_left_arrow.png" + alt="이전" + width={12} + height={22} + className="w-[12px] h-[22px]" + /> + </button> + + + {/* 우측 버튼 */} + <button + type="button" + onClick={() => go(1)} + className="absolute right-[-60px] top-1/2 -translate-y-1/2 z-10 + w-[80px] h-[80px] rounded-full border border-black/20 bg-white + grid place-items-center shadow-sm hover:bg-black/[0.03]" + > + <Image + src="/images/landing/icon_right_arrow.png" + alt="다음" + width={12} + height={22} + className="w-[12px] h-[22px]" + /> + </button> + + + {/* 뷰포트 */} + <div + className={[ + "overflow-hidden", + "select-none touch-pan-y", + "cursor-grab active:cursor-grabbing", + "w-[583px] h-[400px] mx-auto", + ].join(" ")} + onPointerDown={onPointerDown} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + onPointerCancel={onPointerUp} + > + <div + className="flex transition-transform duration-300 ease-out" + style={{ transform: `translateX(-${idx * 100}%)` }} + > + {safeSlides.map((s, i) => ( + <div key={`${s.src}-${i}`} className="min-w-full"> + <Image + src={s.src} + alt={s.alt ?? `slide-${i + 1}`} + width={583} + height={400} + className="w-full h-full object-contain" + style={{ width: "100%", height: "100%" }} + draggable={false} + /> + </div> + ))} + </div> + </div> + </div> + + {/* 도트 */} + {canSlide && ( + <div className="mt-3 flex items-center justify-center gap-2"> + {safeSlides.map((_, i) => { + const active = i === idx; + return ( + <button + key={i} + type="button" + onClick={() => setIdx(i)} + aria-label={`${i + 1}번 슬라이드`} + className={[ + "h-2 w-2 rounded-full", + active ? "bg-black/70" : "bg-black/20 hover:bg-black/35", + ].join(" ")} + /> + ); + })} + </div> + )} + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx b/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx new file mode 100644 index 000000000..f5bc7bbfe --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx @@ -0,0 +1,182 @@ +// apps/dm/app/(landing)/reuse/_components/Lnb.tsx +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useMemo, useState } from "react"; + +export type LnbItem = { + label: string; + href?: string; // href 없는 부모(2뎁스 컨테이너)도 허용 + children?: LnbItem[]; +}; + +function isExactActive(pathname: string, href?: string) { + if (!href) return false; + return pathname === href; +} + +export default function Lnb({ title, items }: { title: string; items: LnbItem[] }) { + const pathname = usePathname(); + + // ✅ 현재 경로가 속한 2depth 그룹을 기본 open + const defaultOpenLabel = useMemo(() => { + const found = items.find((it) => it.children?.some((c) => isExactActive(pathname, c.href))); + return found?.label ?? null; + }, [items, pathname]); + + // ✅ 아코디언: 한 번에 하나만 열리도록 openLabel 1개만 관리 + const [openLabel, setOpenLabel] = useState<string | null>(defaultOpenLabel); + + return ( + <aside + className={[ + "fixed left-0 top-[83px]", // ✅ 화면 고정 + TopNav 아래로 + "w-[260px] min-w-[260px] shrink-0", + "h-[calc(100vh-83px)]", // ✅ 남은 높이만큼 + "overflow-y-auto", // ✅ LNB 내부 스크롤 + "bg-white z-50", // ✅ 겹침 방지 + ].join(" ")} + > + {/* 타이틀 */} + <div + className="h-[120px] flex items-center justify-center + text-white text-[40px] font-bold + bg-gradient-to-r from-cyan-500 to-green-500" + > + {title} + </div> + + {/* 메뉴 */} + <ul> + {items.map((item) => { + const hasChildren = !!item.children?.length; + + // 1뎁스 active + const active1 = isExactActive(pathname, item.href); + + // 2뎁스 active(자식 중 exact) + const active2 = item.children?.some((c) => isExactActive(pathname, c.href)) ?? false; + + const active = active1 || active2; + + // ✅ children 있는 경우: 아코디언 헤더 + 패널 + if (hasChildren && !item.href) { + const isOpen = openLabel === item.label; + + return ( + <li key={item.label}> + {/* 아코디언 헤더 */} + <button + type="button" + onClick={() => setOpenLabel((prev) => (prev === item.label ? null : item.label))} + className={[ + "w-full px-5 py-6", + "flex items-center justify-between", + "border-b border-slate-300", + "text-center text-base font-semibold", + active ? "bg-slate-100 text-slate-900" : "bg-slate-100 text-slate-500", + ].join(" ")} + aria-expanded={isOpen} + > + <span className="w-full text-center">{item.label}</span> + + {/* ✅ 우측 화살표(이미지처럼) */} + <span + aria-hidden + className={[ + "ml-3 shrink-0 transition-transform duration-200", + isOpen ? "rotate-180" : "rotate-0", + ].join(" ")} + > + <Image + src="/images/landing/drop_arrow.svg" + alt="드롭다운 화살표 아이콘" + width={28} + height={28} + className="w-7 h-7" + /> + </span> + </button> + + {/* 패널: height auto 애니메이션(그리드 트릭) */} + <div + className={[ + "grid transition-[grid-template-rows] duration-200 ease-out", + isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]", + ].join(" ")} + > + <div className="overflow-hidden bg-white"> + <ul> + {item.children!.map((child) => { + const childActive = isExactActive(pathname, child.href); + + return ( + <li key={child.href ?? child.label}> + {child.href ? ( + <Link + href={child.href} + className={[ + "block px-5 py-4 pl-[60px] text-[18px] font-medium", + "border-b border-slate-200 transition-colors", + childActive + ? "bg-white text-sky-500 cursor-default" + : "bg-slate-50 text-slate-500 hover:bg-white hover:text-sky-500", + ].join(" ")} + > + {/* 이미지처럼 불릿 필요하면 */} + <span className="mr-2 text-current"> + • + </span> + {child.label} + </Link> + ) : ( + <div className="block px-5 py-4 text-[18px] font-medium border-b border-slate-200 bg-slate-50 text-slate-500"> + {child.label} + </div> + )} + </li> + ); + })} + </ul> + </div> + </div> + </li> + ); + } + + // ✅ 일반 1뎁스 메뉴(기존 유지) + return ( + <li key={item.href ?? item.label}> + {item.href ? ( + <Link + href={item.href} + className={[ + "block px-5 py-4 text-center text-base font-semibold", + "border-b border-slate-300 transition-colors", + active + ? "bg-white text-sky-500 cursor-default" + : "bg-[#F0F0F0] text-[#767676] hover:bg-white hover:text-sky-500", + ].join(" ")} + > + {item.label} + </Link> + ) : ( + <div + className={[ + "block px-5 py-4 text-center text-base font-semibold", + "border-b border-slate-300", + "bg-slate-100 text-slate-500", + ].join(" ")} + > + {item.label} + </div> + )} + </li> + ); + })} + </ul> + </aside> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/LoginStatus.tsx b/apps/dm_new/app/(landing)/reuse/_components/LoginStatus.tsx new file mode 100644 index 000000000..4ddc08812 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/LoginStatus.tsx @@ -0,0 +1,51 @@ +// apps/dm/app/(landing)/reuse/_components/LoginStatus.tsx +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function LoginStatus() { + const router = useRouter(); + const [userName, setUserName] = useState<string | null>(null); + + useEffect(() => { + // 예시: 로그인 시 저장한 값 + const name = localStorage.getItem("userName"); + setUserName(name); + }, []); + + const handleLogout = () => { + localStorage.removeItem("accessToken"); + localStorage.removeItem("userName"); + router.refresh(); // 헤더 상태 갱신 + }; + + // 🔹 로그인 전 + if (!userName) { + return ( + <Link + href="/reuse/login" + className="flex items-center gap-2 rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white hover:bg-sky-600" + > + 로그인 + <span>→</span> + </Link> + ); + } + + // 🔹 로그인 후 + return ( + <div className="flex items-center gap-3 text-sm"> + <span className="text-gray-700"> + <strong>{userName}</strong>님 환영합니다. + </span> + <button + onClick={handleLogout} + className="rounded-md bg-red-500 px-3 py-2 text-white hover:bg-red-600" + > + 로그아웃 + </button> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/PostcodeModal.tsx b/apps/dm_new/app/(landing)/reuse/_components/PostcodeModal.tsx new file mode 100644 index 000000000..83fe82aea --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/PostcodeModal.tsx @@ -0,0 +1,298 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; + +export type AddrItem = { + zipNo: string; // 우편번호 + roadAddr: string; // 도로명주소(전체 문장) + jibunAddr: string; // 지번주소 +}; + +type Props = { + open: boolean; + onClose: () => void; + onSelect: (item: AddrItem) => void; +}; + +type PostcodeApiResponse = { + totalCount?: number; + items?: AddrItem[]; +}; + +function useDebouncedValue<T>(value: T, delay = 250) { + const [v, setV] = useState(value); + useEffect(() => { + const t = setTimeout(() => setV(value), delay); + return () => clearTimeout(t); + }, [value, delay]); + return v; +} + +// A: 추천은 roadAddr 전체 문장 +const normalize = (s: string) => s.replace(/\s+/g, " ").trim(); + +const makeSuggestionsA = (list: AddrItem[], keyword: string, limit = 7) => { + const key = normalize(keyword); + + const roadAddrs = list + .map((x) => x.roadAddr) + .filter((v): v is string => typeof v === "string" && v.trim().length > 0) + .map(normalize); + + const uniq = Array.from(new Set(roadAddrs)); + + // 입력어 포함 우선 + 짧은 주소 우선 + uniq.sort((a, b) => { + const ai = a.includes(key) ? 0 : 1; + const bi = b.includes(key) ? 0 : 1; + if (ai !== bi) return ai - bi; + return a.length - b.length; + }); + + return uniq.slice(0, limit); +}; + +export default function PostcodeModal({ open, onClose, onSelect }: Props) { + const [mounted, setMounted] = useState(false); + + const [q, setQ] = useState(""); + const dq = useDebouncedValue(q, 250); + + const [suggestions, setSuggestions] = useState<string[]>([]); + const [items, setItems] = useState<AddrItem[]>([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + + const inputRef = useRef<HTMLInputElement | null>(null); + + useEffect(() => setMounted(true), []); + + // open 시: body scroll lock + focus + ESC + useEffect(() => { + if (!open) return; + + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + setTimeout(() => inputRef.current?.focus(), 50); + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + return () => { + document.body.style.overflow = prevOverflow; + window.removeEventListener("keydown", onKeyDown); + }; + }, [open, onClose]); + + // 검색/추천/리스트 갱신 + useEffect(() => { + if (!open) return; + + const keyword = dq.trim(); + + if (!keyword) { + setSuggestions([]); + setItems([]); + setTotal(0); + setLoading(false); + return; + } + + let alive = true; + + (async () => { + setLoading(true); + try { + const res = await fetch(`/api/postcode?query=${encodeURIComponent(keyword)}&page=1`, { + cache: "no-store", + }); + + const data = (await res.json()) as PostcodeApiResponse; + + if (!alive) return; + + const list: AddrItem[] = data.items ?? []; + const totalCount: number = data.totalCount ?? 0; + + setItems(list); + setTotal(totalCount); + + // ✅ A: roadAddr 전체 문장 추천 + const sug = makeSuggestionsA(list, keyword, 7); + setSuggestions(sug); + } catch { + if (!alive) return; + setItems([]); + setTotal(0); + setSuggestions([]); + } finally { + if (alive) setLoading(false); + } + })(); + + return () => { + alive = false; + }; + }, [dq, open]); + + const showSuggest = open && q.trim().length > 0 && suggestions.length > 0; + + if (!mounted || !open) return null; + + return createPortal( + <div className="fixed inset-0 z-[9999]"> + {/* overlay */} + <button + type="button" + className="absolute inset-0 bg-black/40" + aria-label="닫기" + onClick={onClose} + /> + + {/* panel wrapper */} + <div className="absolute inset-0 flex items-center justify-center p-6"> + <div + className="relative w-full max-w-[520px] rounded-2xl bg-white shadow-[0_20px_60px_rgba(0,0,0,0.18)]" + role="dialog" + aria-modal="true" + aria-label="우편번호 검색" + > + {/* header */} + <div className="flex items-center justify-between px-7 pt-6 pb-4"> + <h2 className="text-[22px] font-extrabold tracking-tight text-slate-900"> + 우편번호 검색 + </h2> + + <button + type="button" + onClick={onClose} + className="grid h-9 w-9 place-items-center rounded-full hover:bg-slate-100" + aria-label="닫기" + > + <span className="text-[18px] leading-none">✕</span> + </button> + </div> + + {/* search input */} + <div className="px-7 pb-4"> + <div className="relative"> + <input + ref={inputRef} + value={q} + onChange={(e) => setQ(e.target.value)} + placeholder="주소검색" + className={[ + "w-full rounded-md border px-4 py-3 pr-12 text-[14px] outline-none", + "border-slate-300 focus:border-sky-500 focus:ring-2 focus:ring-sky-200", + ].join(" ")} + /> + + <button + type="button" + className="absolute right-3 top-1/2 -translate-y-1/2 grid h-9 w-9 place-items-center rounded-full hover:bg-slate-100 text-[#01ACE5]" + aria-label="검색" + onClick={() => setQ((v) => v.trim())} + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> + <path + d="M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z" + stroke="currentColor" + strokeWidth="2" + /> + <path + d="M21 21l-4.3-4.3" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + /> + </svg> + </button> + + {/* suggestions dropdown */} + {showSuggest && ( + <div className="absolute left-0 right-0 top-[calc(100%+6px)] z-10 overflow-hidden rounded-md border border-slate-200 bg-white shadow-lg"> + {suggestions.map((s) => ( + <button + key={s} + type="button" + className="flex w-full items-center justify-between px-4 py-3 text-left text-[14px] hover:bg-slate-50" + onClick={() => { + // ✅ 추천 클릭 → 입력값 채우고 드롭다운 닫기 + 포커스 유지 + setQ(s); + setSuggestions([]); + requestAnimationFrame(() => inputRef.current?.focus()); + }} + > + <span className="truncate text-slate-800">{s}</span> + <span className="ml-4 text-slate-400">↵</span> + </button> + ))} + </div> + )} + </div> + </div> + + {/* results */} + <div className="px-7 pb-7"> + <div className="mb-3 text-[13px] text-slate-500"> + {q.trim() + ? loading + ? "검색 중…" + : `총 ${total.toLocaleString()}개의 검색결과` + : "주소를 입력하면 검색결과가 표시됩니다."} + </div> + + <div className="max-h-[420px] overflow-y-auto pr-1"> + {!loading && q.trim() && items.length === 0 && ( + <div className="rounded-lg border border-slate-200 p-4 text-[14px] text-slate-600"> + 검색 결과가 없습니다. + </div> + )} + + <div className="space-y-3"> + {items.map((it) => ( + <button + key={`${it.zipNo}-${it.roadAddr}`} + type="button" + onClick={() => onSelect(it)} + className={[ + "w-full rounded-lg border p-4 text-left transition", + "border-slate-200 hover:border-sky-300 hover:bg-sky-50/30", + ].join(" ")} + > + <div className="text-[20px] font-extrabold tracking-tight text-slate-900"> + {it.zipNo} + </div> + + <div className="mt-2 flex items-start gap-2"> + <span className="w-[52px] shrink-0 mt-[2px] inline-flex items-center justify-center rounded-full border border-sky-400 px-2 py-[2px] text-[12px] font-semibold text-sky-600"> + 도로명 + </span> + <p className="text-[14px] leading-6 text-slate-700"> + {it.roadAddr} + </p> + </div> + + <div className="mt-1 flex items-start gap-2"> + <span className="w-[52px] shrink-0 mt-[2px] inline-flex items-center justify-center rounded-full border border-[#6DBB4F] px-2 py-[2px] text-[12px] font-semibold text-[#6DBB4F]"> + 지번 + </span> + <p className="text-[14px] leading-6 text-slate-700"> + {it.jibunAddr} + </p> + </div> + </button> + ))} + </div> + </div> + </div> + </div> + </div> + </div>, + document.body + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/_components/SideIndicator.tsx b/apps/dm_new/app/(landing)/reuse/_components/SideIndicator.tsx new file mode 100644 index 000000000..8f4cea164 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/SideIndicator.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import styles from "../reuse.module.css"; + +export interface SideIndicatorItem { + id: string; + label: string; // 상단/공용 라벨 + sideLabel?: string; // 사이드 인디게이터 전용 라벨(옵션) +} + +export default function SideIndicator({ + items, + activeId, +}: { + items: SideIndicatorItem[]; + activeId: string; +}) { + const [manualActiveId, setManualActiveId] = useState<string | null>(null); + const clearTimerRef = useRef<number | null>(null); + + const currentId = (manualActiveId ?? activeId ?? "main") || "main"; + + // ✅ manualActiveId가 observer(activeId)와 불일치하기 시작하면 즉시 해제 (stuck 방지) + useEffect(() => { + if (!manualActiveId) return; + if (activeId && activeId !== manualActiveId) setManualActiveId(null); + }, [activeId, manualActiveId]); + + // ✅ fallback: 스무스 스크롤 시간이 길어도 manual이 오래 남지 않게 타임아웃 해제 + useEffect(() => { + return () => { + if (clearTimerRef.current) window.clearTimeout(clearTimerRef.current); + }; + }, []); + + const scrollTo = (id: string) => { + setManualActiveId(id); + + // 기존 타이머 제거 후 새로 설정 + if (clearTimerRef.current) window.clearTimeout(clearTimerRef.current); + clearTimerRef.current = window.setTimeout(() => { + setManualActiveId(null); + }, 1600); + + if (id === "main") { + window.scrollTo({ top: 0, behavior: "smooth" }); + return; + } + + document.getElementById(id)?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }; + + return ( + <aside className={styles.sideIndicator} aria-label="섹션 이동"> + <div className={styles.sideIndicatorLine} aria-hidden="true" /> + + <ul className={styles.sideIndicatorList}> + {items.map((it) => { + const isActive = it.id === currentId; + const isMain = it.id === "main"; + const displayLabel = it.sideLabel ?? it.label; + + return ( + <li key={it.id} className={styles.sideIndicatorItem}> + <button + type="button" + onClick={() => scrollTo(it.id)} + className={[ + styles.indicatorBtn, + isActive ? styles.indicatorActive : styles.indicatorDot, + isActive + ? isMain + ? styles.indicatorMainActive + : styles.indicatorSectionActive + : "", + ].join(" ")} + aria-current={isActive ? "location" : undefined} + aria-label={displayLabel} + > + {/* ✅ 텍스트 중복 렌더링 제거: + - active일 때만 시각적 텍스트 표시 + - 비활성은 SR-only로만 제공 (CSS에 따라 필요 없으면 제거 가능) */} + {isActive ? ( + <span className={styles.indicatorText}>{displayLabel}</span> + ) : ( + <span className="sr-only">{displayLabel}</span> + )} + </button> + </li> + ); + })} + </ul> + </aside> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/SubPageHeader.tsx b/apps/dm_new/app/(landing)/reuse/_components/SubPageHeader.tsx new file mode 100644 index 000000000..b18ee28a6 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/SubPageHeader.tsx @@ -0,0 +1,64 @@ +"use client"; + +import Link from "next/link"; + +export type Crumb = { label: string; href?: string }; + +type Props = { + title: string; + crumbs: Crumb[]; + note?: string; + underlineWidthClass?: string; + className?: string; + wrapClassName?: string; + hideFooterLine?: boolean; + hideGuideText?: boolean; +}; + +export default function SubPageHeader({ + title, + crumbs, + note = "* 는 필수 값입니다.", + // underlineWidthClass = "w-40", + className = "", + wrapClassName = "", + // hideFooterLine = false, + hideGuideText = false, +}: Props) { + return ( + <section className={`text-black ${className}`}> + <div className={`pb-5 ${wrapClassName}`}> + <div className="flex items-start justify-between gap-6"> + {/* 좌측 타이틀 */} + <div className="inline-block"> + <h1 className="relative z-10 px-2 text-[28px] font-extrabold tracking-tight text-slate-900 leading-none">{title}</h1> + <div className="relative top-[-7px] -z-1 h-4 bg-gradient-to-r from-sky-300 to-green-300" /> + </div> + + {/* 우측 브레드크럼 */} + <nav className="pt-2 text-sm text-black/70" aria-label="breadcrumb"> + {crumbs.map((c, i) => ( + <span key={`${c.label}-${i}`}> + {c.href ? ( + <Link href={c.href} className="hover:text-sky-500"> + {c.label} + </Link> + ) : ( + <span className="text-black/90">{c.label}</span> + )} + {i < crumbs.length - 1 && <span className="mx-2">›</span>} + </span> + ))} + </nav> + </div> + {/* 하단 라인 + 안내문 */} + {!hideGuideText && ( + <div className="mt-6 border-t border-black/10 pt-4 text-sm text-black/70"> + <span className="text-red-400">*</span> + {note.replace("*", "")} + </div> + )} + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_components/TopNav.tsx b/apps/dm_new/app/(landing)/reuse/_components/TopNav.tsx new file mode 100644 index 000000000..17c6f5e04 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_components/TopNav.tsx @@ -0,0 +1,482 @@ +// apps/dm/app/(landing)/reuse/_components/TopNav.tsx +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useId, useState } from "react"; +import { useRouter } from "next/navigation"; + +const NAV_ITEMS = [ + { href: "/reuse/intro/law", label: "물 재이용 소개" }, + { href: "/reuse/cases/rain", label: "물 재이용 현황" }, + { href: "/reuse/business/rain/suwon", label: "물 재이용 사례" }, + { href: "/reuse/apply/place", label: "수요신청" }, +]; + +// ✅ 필요 시 실제 “수요처등록” 페이지로 경로만 교체하세요. +const REGISTER_HREF = "/reuse/apply/place"; + +// ✅ 로그인 정보 저장 키(원하는대로 변경 가능) +const LS_TOKEN_KEY = "reuse_accessToken"; +const LS_USER_KEY = "reuse_userName"; + +type LoginModalProps = { + open: boolean; + onClose: () => void; + onSuccess?: (payload: { userName: string; accessToken?: string }) => void; +}; + +type FieldKey = "bizNo" | "workerNo" | "pw"; + +function isBizNoValid(v: string) { + // 예: 123-45-67890 + return /^\d{3}-\d{2}-\d{5}$/.test(v); +} + +function inputCls(opts: { hasError?: boolean; disabled?: boolean }) { + const base = + "w-full h-[48px] rounded-[4px] border px-5 text-[18px] outline-none"; + if (opts.disabled) return `${base} border-gray-200 bg-gray-50 text-gray-400`; + if (opts.hasError) + return `${base} border-red-500 focus:border-red-500 ring-1 ring-red-400`; + return `${base} border-gray-300 focus:border-sky-400`; +} + +function DemandLoginModal({ open, onClose, onSuccess }: LoginModalProps) { + const titleId = useId(); + const router = useRouter(); + + const [bizNo, setBizNo] = useState(""); + const [workerNo, setWorkerNo] = useState(""); + const [noWorker, setNoWorker] = useState(false); + const [pw, setPw] = useState(""); + + // ✅ 유효성/서버 에러 표시용 + const [errors, setErrors] = useState<Partial<Record<FieldKey, string>>>({}); + const [touched, setTouched] = useState<Partial<Record<FieldKey, boolean>>>({}); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + // 스크롤 잠금 + const prev = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + // 모달 오픈 시 에러/터치 초기화 + setErrors({}); + setTouched({}); + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + useEffect(() => { + if (noWorker) { + setWorkerNo(""); + setErrors((p) => { + const { ...rest } = p; + delete rest.workerNo; + return rest; + }); + } + }, [noWorker]); + + if (!open) return null; + + const validate = () => { + const next: Partial<Record<FieldKey, string>> = {}; + + const b = bizNo.trim(); + const w = workerNo.trim(); + const p = pw.trim(); + + if (!b) next.bizNo = "사업자등록번호를 입력해주세요."; + else if (!isBizNoValid(b)) + next.bizNo = "사업자등록번호 형식이 올바르지 않습니다. (예: 123-45-67890)"; + + if (!noWorker && !w) next.workerNo = "종사자등록번호를 입력해주세요."; + + if (!p) next.pw = "비밀번호를 입력해주세요."; + + setErrors(next); + return next; + }; + + const markTouched = (key: FieldKey) => + setTouched((p) => ({ ...p, [key]: true })); + + const markAllTouched = () => + setTouched({ bizNo: true, workerNo: true, pw: true }); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + markAllTouched(); + + const v = validate(); + if (Object.keys(v).length) return; + + try { + setSubmitting(true); + + // ✅ TODO: 실제 로그인 API 연결 지점 + // const res = await fetch("/api/demand/login", { + // method: "POST", + // headers: { "Content-Type": "application/json" }, + // body: JSON.stringify({ bizNo, workerNo: noWorker ? null : workerNo, pw }), + // }); + // if (!res.ok) { ... } + // const data = await res.json(); + // onSuccess?.({ userName: data.userName, accessToken: data.token }); + + // --- 데모: 비밀번호가 "1234"면 성공 처리(원하면 제거) --- + const serverOk = pw.trim() === "1234"; + if (!serverOk) { + setErrors((p) => ({ ...p, pw: "잘못된 비밀번호입니다. (데모: 1234 입력 시 성공)" })); + setTouched((p) => ({ ...p, pw: true })); + return; + } + + onSuccess?.({ userName: "마인드원", accessToken: "demo_token" }); + onClose(); + router.refresh(); + } finally { + setSubmitting(false); + } + }; + + return ( + <div className="fixed inset-0 z-[100]"> + {/* Backdrop */} + <button + type="button" + aria-label="닫기" + onClick={onClose} + className="absolute inset-0 bg-black/40" + /> + + {/* Dialog */} + <div className="absolute inset-0 flex items-center justify-center"> + <div + role="dialog" + aria-modal="true" + aria-labelledby={titleId} + className="w-full max-w-[600px] rounded-[16px] bg-white shadow-2xl overflow-hidden" + > + {/* Header */} + <div className="relative px-10 pt-10"> + <button + type="button" + onClick={onClose} + aria-label="닫기" + className="absolute right-8 top-8 z-40 h-10 w-10 grid place-items-center rounded-full hover:bg-gray-100" + > + <span className="text-2xl leading-none">×</span> + </button> + + <h2 + id={titleId} + className="relative z-10 px-2 text-center text-[32px] font-bold tracking-tight leading-none text-gray-900" + > + 수요처 로그인 + </h2> + + {/* title underline gradient bar */} + <div className="relative top-[-7px] -z-1 mx-auto h-[10px] w-[240px] bg-gradient-to-r from-sky-300 to-green-300" /> + </div> + + {/* Body */} + <form onSubmit={submit} className="px-14 pb-12 pt-10"> + <div className="space-y-7"> + {/* 사업자 등록번호 */} + <div> + <label className="block text-[18px] font-bold text-gray-900 mb-3"> + 사업자 등록번호 + </label> + <input + value={bizNo} + onChange={(e) => { + setBizNo(e.target.value); + setErrors((p) => ({ ...p, bizNo: undefined })); + }} + onBlur={() => { + markTouched("bizNo"); + validate(); + }} + placeholder="사업자등록번호를 입력하세요. (예: 123-45-67890)" + className={inputCls({ hasError: !!(touched.bizNo && errors.bizNo) })} + /> + {touched.bizNo && errors.bizNo && ( + <p className="mt-3 text-[14px] font-medium text-red-500"> + {errors.bizNo} + </p> + )} + </div> + + {/* 종사자등록번호 + 해당없음 */} + <div> + <div className="flex items-center justify-between mb-3"> + <label className="block text-[18px] font-bold text-gray-900"> + 종사자등록번호 + </label> + + <label className="flex items-center gap-1 text-[16px] text-[#4F4F4F] select-none"> + <input + type="checkbox" + checked={noWorker} + onChange={(e) => setNoWorker(e.target.checked)} + className="h-4 w-4 accent-[#004A94]" + /> + 해당없음 + </label> + </div> + + <input + value={workerNo} + onChange={(e) => { + setWorkerNo(e.target.value); + setErrors((p) => ({ ...p, workerNo: undefined })); + }} + onBlur={() => { + markTouched("workerNo"); + validate(); + }} + disabled={noWorker} + placeholder="종사자등록번호를 입력하세요." + className={inputCls({ + disabled: noWorker, + hasError: !noWorker && !!(touched.workerNo && errors.workerNo), + })} + /> + + {!noWorker && touched.workerNo && errors.workerNo && ( + <p className="mt-3 text-[14px] font-medium text-red-500"> + {errors.workerNo} + </p> + )} + </div> + + {/* 비밀번호 */} + <div> + <label className="block text-[18px] font-bold text-gray-900 mb-3"> + 비밀번호 + </label> + <input + type="password" + value={pw} + onChange={(e) => { + setPw(e.target.value); + setErrors((p) => ({ ...p, pw: undefined })); + }} + onBlur={() => { + markTouched("pw"); + validate(); + }} + placeholder="비밀번호를 입력하세요." + className={inputCls({ hasError: !!(touched.pw && errors.pw) })} + /> + {touched.pw && errors.pw && ( + <p className="mt-3 text-[14px] font-medium text-red-500"> + {errors.pw} + </p> + )} + </div> + + {/* 로그인 버튼 */} + <button + type="submit" + disabled={submitting} + className={[ + "mt-2 w-full h-[60px] rounded-[4px] text-white text-[18px]", + submitting + ? "bg-sky-300 cursor-not-allowed" + : "bg-sky-500 hover:bg-sky-600 active:bg-sky-700", + ].join(" ")} + > + {submitting ? "로그인 중..." : "로그인"} + </button> + + {/* 비밀번호 초기화 요청 */} + <button + type="button" + onClick={() => alert("TODO: 비밀번호 초기화 요청 화면/기능 연결")} + className="mx-auto block text-[14px] text-[#767676] hover:text-gray-900" + > + 비밀번호 초기화 요청 + </button> + + {/* 하단 안내 박스 + 수요처등록 버튼 */} + <div className="mt-6 border border-[#004A94] bg-[#EEF3F9] px-4 py-4 flex items-center justify-between gap-6"> + <div className="leading-snug"> + <div className="text-[16px] text-[#8698AA]"> + 수요처를 등록하지 않았다면 + </div> + <div className="text-[18px] text-[#004A94]"> + 먼저 수요처등록을 해주세요. + </div> + </div> + + <button + type="button" + onClick={() => router.push(REGISTER_HREF)} + className="shrink-0 rounded-[4px] h-[48px] px-10 bg-[#004A94] text-white text-[18px] hover:opacity-95" + > + 수요처등록 + </button> + </div> + </div> + </form> + </div> + </div> + </div> + ); +} + +export default function TopNav() { + const router = useRouter(); + + const [loginOpen, setLoginOpen] = useState(false); + const [userName, setUserName] = useState<string | null>(null); + + // ✅ 초기 로드 시 로그인 상태 반영 + useEffect(() => { + const name = localStorage.getItem(LS_USER_KEY); + setUserName(name); + }, []); + + const openLogin = () => setLoginOpen(true); + + const logout = () => { + localStorage.removeItem(LS_TOKEN_KEY); + localStorage.removeItem(LS_USER_KEY); + setUserName(null); + router.refresh(); + }; + + return ( + <> + <header className="fixed top-0 left-0 right-0 z-50 bg-white backdrop-blur border-b shadow-md min-w-[1800px]"> + {/* ✅ 좌/중/우 정렬이 되도록 컨테이너 구성 */} + <div className="px-6"> + <div className="relative h-[83px] flex items-center justify-between"> + {/* 좌측: 로고 */} + <div className="flex items-center gap-3"> + <Link href="/reuse" className="flex items-center"> + <Image + src="/images/landing/logo.png" + alt="HASUON" + width={229} + height={46} + className="h-9 w-auto" + priority + /> + </Link> + </div> + + {/* 중앙: 상단 메뉴 */} + <nav className="absolute left-1/2 -translate-x-1/2 flex items-center gap-[40px] text-sm font-medium"> + {NAV_ITEMS.map((it) => { + // ✅ 수요신청: 로그인 전이면 로그인 모달, 로그인 후면 페이지 이동 + if (it.label === "수요신청") { + return ( + <button + key={it.href} + type="button" + onClick={() => { + if (!userName) openLogin(); + else router.push(it.href); + }} + className={[ + "relative px-3 py-2 text-lg font-bold", + "text-gray-800 hover:text-sky-700", + ].join(" ")} + > + {it.label} + </button> + ); + } + + return ( + <Link + key={it.href} + href={it.href} + className={[ + "relative px-3 py-2 text-lg font-bold", + "text-gray-800 hover:text-sky-700", + ].join(" ")} + > + {it.label} + </Link> + ); + })} + </nav> + + {/* ✅ 우측: 로그인 버튼 / 로그인 정보 + 로그아웃 */} + <div className="flex items-center gap-3"> + {!userName ? ( + <button + type="button" + onClick={openLogin} + className="inline-flex items-center gap-2 rounded-md bg-sky-500 px-4 py-2 text-base text-white hover:bg-sky-600" + > + 로그인 + <span aria-hidden> + <Image + src="/images/landing/icon_login.png" + alt="로그인 아이콘" + width={24} + height={24} + className="h-6 w-6 object-contain ml-1" + /> + </span> + </button> + ) : ( + <> + <span className="text-base text-gray-700"> + <strong className="text-xl text-emerald-600">{userName}</strong>님 환영합니다. + </span> + <button + type="button" + onClick={logout} + className="inline-flex items-center gap-2 rounded-md bg-red-500 px-4 py-2 text-base text-white hover:bg-red-600" + > + 로그아웃 + <span aria-hidden> + <Image + src="/images/landing/icon_logout.png" + alt="로그아웃 아이콘" + width={24} + height={24} + className="h-6 w-6 object-contain" + /> + </span> + </button> + </> + )} + </div> + </div> + </div> + </header> + + {/* ✅ 로그인 모달 */} + <DemandLoginModal + open={loginOpen} + onClose={() => setLoginOpen(false)} + onSuccess={({ userName, accessToken }) => { + // ✅ 로그인 성공 처리: 저장 + 헤더 갱신 + localStorage.setItem(LS_USER_KEY, userName); + if (accessToken) localStorage.setItem(LS_TOKEN_KEY, accessToken); + setUserName(userName); + setLoginOpen(false); + router.refresh(); + }} + /> + </> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_constants/lnb.ts b/apps/dm_new/app/(landing)/reuse/_constants/lnb.ts new file mode 100644 index 000000000..671dabd65 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_constants/lnb.ts @@ -0,0 +1,58 @@ +// apps/dm/app/(landing)/reuse/_constants/lnb.ts +import type { LnbItem } from "../_components/Lnb"; + +export const LNB_DEMAND: LnbItem[] = [ + { href: "/reuse/apply/place", label: "수요처" }, + { href: "/reuse/apply/register", label: "수요정보등록" }, + { href: "/reuse/apply/status", label: "수요신청현황" }, +]; + +export const LNB_INTRO = [ + { label: "물 재이용 법", href: "/reuse/intro/law" }, + { label: "빗물 이용시설", href: "/reuse/intro/rain" }, + { label: "중수도", href: "/reuse/intro/greywater" }, + { label: "하수처리수", href: "/reuse/intro/treated" }, +]; + +export const LNB_CASES = [ + { label: "빗물이용시설(현황)", href: "/reuse/cases/rain" }, + { label: "중수도(현황)", href: "/reuse/cases/greywater" }, + { label: "하수처리수 재이용시설(현황)", href: "/reuse/cases/treated" }, +]; + +// 물 재이용 사례처럼 2depth가 있는 메뉴 +export const LNB_BUSINESS: LnbItem[] = [ + { + label: "빗물 재이용 사례", + children: [ + { label: "경기도 수원시", href: "/reuse/business/rain/suwon" }, + // { label: "충청남도 보령시", href: "/reuse/business/rain/bolyeong" }, + // { label: "충청북도 청주시", href: "/reuse/business/rain/cheongju" }, + ], + }, + { + label: "중수 재이용 사례", + children: [{ label: "사례 목록", href: "/reuse/business/graywater" }], + }, + { + label: "하수 재이용 사례", + children: [ + { label: "파주시", href: "/reuse/business/sewage/paju" }, + { label: "충청남도 보령시", href: "/reuse/business/sewage/bolyeong" }, + { label: "여수", href: "/reuse/business/sewage/yeosu" }, + { label: "청주", href: "/reuse/business/sewage/cheongju" }, + { label: "수원시 황구지천", href: "/reuse/business/sewage/hwanggu" }, + { label: "구리시 도로자동청소시스템", href: "/reuse/business/sewage/guri" }, + ], + }, + { + label: "해외 재이용 사례", + children: [ + { label: "사례 개요", href: "/reuse/business/globalCase/overview" }, + { label: "대만", href: "/reuse/business/globalCase/taiwan" }, + { label: "호주", href: "/reuse/business/globalCase/australia" }, + { label: "싱가포르", href: "/reuse/business/globalCase/singapore" }, + { label: "EU", href: "/reuse/business/globalCase/eu" } + ], + }, +]; diff --git a/apps/dm_new/app/(landing)/reuse/_hooks/useScrollSpy.ts b/apps/dm_new/app/(landing)/reuse/_hooks/useScrollSpy.ts new file mode 100644 index 000000000..821e7e166 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_hooks/useScrollSpy.ts @@ -0,0 +1,66 @@ +/** + * IntersectionObserver로 각 section의 화면 노출 비율을 계산해서 가장 많이 보이는 section을 activeId로 만든다 + */ +"use client"; +import { useEffect, useRef, useState } from "react"; + +const DEFAULT_THRESHOLD = [0, 0.01, 0.1, 0.25, 0.5, 0.75]; + +export function useScrollSpy(sectionIds: string[], options?: IntersectionObserverInit) { + const [activeId, setActiveId] = useState(sectionIds[0] ?? ""); + const ratiosRef = useRef<Map<string, number>>(new Map()); + const activeIdRef = useRef(activeId); + + useEffect(() => { + activeIdRef.current = activeId; + }, [activeId]); + + const threshold = options?.threshold ?? DEFAULT_THRESHOLD; + const thresholdKey = Array.isArray(threshold) ? threshold.join("|") : String(threshold); + + useEffect(() => { + const els = sectionIds + .map((id) => document.getElementById(id)) + .filter(Boolean) as HTMLElement[]; + + if (!els.length) return; + + const DEFAULT_ROOT_MARGIN = "83px 0px -65% 0px"; + + // 초기값 세팅(안정화) + ratiosRef.current.clear(); + els.forEach((el) => ratiosRef.current.set(el.id, 0)); + + const io = new IntersectionObserver( + (entries) => { + for (const e of entries) { + const id = (e.target as HTMLElement).id; + ratiosRef.current.set(id, e.isIntersecting ? e.intersectionRatio : 0); + } + + // ✅ 전체 섹션 중 가장 ratio 큰 항목을 active로 + let bestId = activeIdRef.current; + let bestRatio = -1; + + for (const [id, ratio] of ratiosRef.current.entries()) { + if (ratio > bestRatio) { + bestRatio = ratio; + bestId = id; + } + } + + if (bestId) setActiveId(bestId); + }, + { + root: options?.root ?? null, + threshold, + rootMargin: options?.rootMargin ?? DEFAULT_ROOT_MARGIN, + } + ); + + els.forEach((el) => io.observe(el)); + return () => io.disconnect(); + }, [sectionIds, options?.root, options?.rootMargin, threshold, thresholdKey]); + + return activeId; +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/Cases.tsx b/apps/dm_new/app/(landing)/reuse/_sections/Cases.tsx new file mode 100644 index 000000000..757950369 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_sections/Cases.tsx @@ -0,0 +1,69 @@ +// apps/dm/app/(landing)/reuse/_sections/Cases.tsx +"use client"; + +import Image from "next/image"; +import section03 from "@/public/images/landing/section_03.png"; + +export default function Cases() { + return ( + <section id="cases" className="bg-[#EAF6F6]"> + <div className="mx-auto max-w-[1520px] px-6 h-[870px] flex items-center"> + <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 w-full"> + + {/* 좌측 : 카피 영역 */} + <div className="flex flex-col justify-center"> + <div className="pl-2 lg:pl-10"> + <p className="text-[32px] font-extrabold text-slate-900 tracking-[-0.02em]"> + 빗물+중수도+하수도 + </p> + + <h2 className="mt-6 leading-[1.1] tracking-[-0.03em]"> + <span className="block text-[64px] font-extrabold text-slate-900"> + 데이터로 보는 + </span> + <span className="mt-3 block text-[64px] font-extrabold text-[#004A94]"> + 물 재이용 현황 + </span> + </h2> + + <div className="mt-14"> + <a + href="/reuse/cases/rain" + className=" + inline-flex items-center justify-center gap-3 + w-[260px] h-[52px] + bg-[#01ACE5] text-white + rounded-sm + hover:bg-[#0A78C0] + transition-colors + " + > + 물 재이용 현황 보러가기 + <span className="text-lg">→</span> + </a> + </div> + </div> + </div> + + {/* 우측 : 이미지 카드 */} + <div className="flex justify-center lg:justify-end"> + <div + className=" + relative + w-full max-w-[720px] + " + > + <Image + src={section03} + alt="물 재이용 현황 데이터 테이블" + className="w-full h-auto object-cover" + style={{ width: "100%", height: "auto" }} + /> + </div> + </div> + + </div> + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx b/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx new file mode 100644 index 000000000..c520f5aea --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx @@ -0,0 +1,36 @@ +// apps/dm/app/(landing)/reuse/_sections/Hero.tsx +import Image from "next/image"; +import styles from "../reuse.module.css"; + +export default function Hero() { + return ( + <section id="main" className={styles.hero}> + <div className={styles.heroBg} aria-hidden /> + <div className={styles.heroOverlay} aria-hidden /> + + <div className="absolute inset-0 z-10 flex items-center justify-center"> + <div className="flex flex-col items-center justify-items-center gap-[40px] mx-auto max-w-[1520px] px-6 text-center text-white"> + <h1 className="text-5xl font-bold tracking-tight">물 재이용</h1> + <p className="text-xl font-medium leading-6 text-white/85 max-w-[720px] mx-auto"> + 빗물, 오수, 하수처리수, 폐수처리수, 발전소 온배수를 재이용처리하여<br /> + 생활, 공업, 농업, 조경, 하천유지 등의 용도로 재이용합니다. + </p> + <p className="text-lg leading-6 text-white/85 max-w-[720px] mx-auto"> + 전국 하수 재이용률 15.6%(2024년 통계 기준),<br /> + 물재이용 활성화를 위한 재이용 수요신청, 매칭을 추진하고 있습니다. + </p> + </div> + </div> + <div className="absolute inset-x-0 bottom-[120px] flex flex-col items-center justify-items-center gap-2 mt-10"> + <Image + src="/images/landing/scroll_down.svg" + alt="" + width={26} + height={40} + className="w-[26px] h-[40px]" + /> + <span className="text-white/80 text-base tracking-[.25em]">scroll down</span> + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx b/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx new file mode 100644 index 000000000..c8c69ad7b --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx @@ -0,0 +1,93 @@ +// apps/dm/app/(landing)/reuse/_sections/IntroCards.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import Link from "next/link"; +import card01 from "@/public/images/landing/card_01.jpg"; +import card02 from "@/public/images/landing/card_02.jpg"; +import card03 from "@/public/images/landing/card_03.jpg"; +import card04 from "@/public/images/landing/card_04.jpg"; + +type IntroCardItem = { + title: string; + desc: string; + image: StaticImageData; + href: string; +}; + +const INTRO_CARDS: IntroCardItem[] = [ + { + title: "물 재이용 법", + desc: "물의 재이용 촉진 및 지원에 관한 법률을 시행하고, 일부 법률은 개정되었습니다.", + image: card01, + href: "/reuse/intro/law", + }, + { + title: "친환경 수자원 빗물", + desc: "가뭄을 극복하고 홍수를 예방하기 위해 비가 내릴 때 빗물을 모아두었다가 사용합니다.", + image: card02, + href: "/reuse/intro/rain", + }, + { + title: "알뜰하게 재이용되는 중수", + desc: "한번 사용한 물을 중수도 시설을 통해 재생하여 개별적 또는 지역적으로 재이용할 수 있습니다.", + image: card03, + href: "/reuse/intro/greywater", + }, + { + title: "하수처리수 재이용", + desc: "사용하고 버려진 공공하수처리장으로 모여진 물을 용도에 맞는 처리 후 재이용 할 수 있습니다.", + image: card04, + href: "/reuse/intro/treated", + }, +]; + +export default function IntroCards() { + return ( + <section id="intro" className="bg-white"> + <div className="mx-auto max-w-[1520px] px-6 py-[90px] h-[870px]"> + <div className="text-center"> + <div className="inline-block"> + <h2 className="relative z-10 px-2 text-[40px] font-extrabold tracking-tight text-slate-900 leading-none">물 재이용 소개</h2> + <div className="relative top-[-7px] -z-1 h-4 bg-gradient-to-r from-sky-300 to-green-300"></div> + </div> + <p className="mt-5 text-xl text-gray-500"> + 지속 가능한 미래를 위한 약속, 깨끗한 물의 새로운 순환을 시작합니다. + </p> + </div> + + <section className="mx-auto max-w-8xl px-4 py-12"> + <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4"> + {INTRO_CARDS.map((card, idx) => ( + <Link + key={idx} + href={card.href} + aria-label={`${card.title} 상세로 이동`} + className="group relative h-[280px] md:h-[320px] lg:h-[500px] overflow-hidden rounded-2xl cursor-pointer focus:outline-none focus:ring-2 focus:ring-sky-400/60" + > + {/* 배경 이미지 (이미 그라디언트 포함) */} + <Image + src={card.image} + alt={card.title} + fill + sizes="(min-width: 1024px) 25vw, (min-width: 768px) 50vw, 100vw" + className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 hover:scale-105" + /> + + {/* 텍스트 영역 */} + <div className="absolute bottom-0 z-10 p-6 text-white"> + <h3 className="text-lg font-semibold tracking-tight"> + {card.title} + </h3> + <p className="mt-2 text-sm leading-relaxed text-white/90 line-clamp-3"> + {card.desc} + </p> + </div> + </Link> + ))} + </div> + </section> + </div> + </section> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/ProcessFlow.tsx b/apps/dm_new/app/(landing)/reuse/_sections/ProcessFlow.tsx new file mode 100644 index 000000000..38b005f55 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/_sections/ProcessFlow.tsx @@ -0,0 +1,22 @@ +// apps/dm/app/(landing)/reuse/_sections/ProcessFlow.tsx +import CaseTabsCarousel from "../_components/CaseTabsCarousel"; + + +export default function ProcessFlow() { + return ( + <section id="processflow" className="bg-white"> + <div className="mx-auto max-w-[1520px] px-6 py-[50px] h-[870px]"> + <div className="text-center"> + <div className="inline-block"> + <h2 className="relative z-10 px-2 text-[40px] font-extrabold tracking-tight text-slate-900 leading-none">물 재이용 사례</h2> + <div className="relative top-[-7px] -z-1 h-4 bg-gradient-to-r from-sky-300 to-green-300"></div> + </div> + </div> + + {/* 탭메뉴 영역 */} + <CaseTabsCarousel /> + + </div> + </section> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/apply/layout.tsx b/apps/dm_new/app/(landing)/reuse/apply/layout.tsx new file mode 100644 index 000000000..e9e7e5cb0 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/layout.tsx @@ -0,0 +1,13 @@ +// 예) apps/dm/app/(landing)/reuse/apply/layout.tsx +import type { ReactNode } from "react"; +import Lnb from "../_components/Lnb"; +import { LNB_DEMAND } from "../_constants/lnb"; + +export default function ApplyLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb title="수요신청" items={LNB_DEMAND} /> + <main className="flex-1 px-[100px] pt-[80px]">{children}</main> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx b/apps/dm_new/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx new file mode 100644 index 000000000..75253392a --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +export type CandidateSite = { + id: string; + name: string; + sewageM3d: number; // 하수처리량 + reuseM3d: number; // 재이용량 + // 지도 연동 시 사용(현재는 UI/구조용) + lat?: number; + lng?: number; +}; + +type Props = { + open: boolean; + onClose: () => void; + onApply: (selected: CandidateSite[]) => void; + initialSelected?: CandidateSite[]; + maxSelect?: number; // 기본 3개 +}; + +const MOCK_SITES: CandidateSite[] = [ + { id: "1", name: "서호생태 수자원 재이용1", sewageM3d: 1300, reuseM3d: 1300, lat: 35.0, lng: 128.0 }, + { id: "2", name: "서호생태 수자원 재이용2", sewageM3d: 1300, reuseM3d: 1300, lat: 35.01, lng: 128.01 }, + { id: "3", name: "서호생태 수자원 재이용3", sewageM3d: 1300, reuseM3d: 1300, lat: 35.02, lng: 128.02 }, +]; + +export default function CandidateSiteModal({ + open, + onClose, + onApply, + initialSelected = [], + maxSelect = 3, +}: Props) { + const [q, setQ] = useState(""); + const [selected, setSelected] = useState<CandidateSite[]>(initialSelected); + + // 지도 마커 컴포넌트 + function MapMarker({ label }: { label?: string }) { + return ( + <div className="relative -translate-x-1/2 -translate-y-full"> + <svg width="72" height="66" viewBox="0 0 72 92"> + <defs> + <linearGradient id="pinGrad" x1="0" y1="0" x2="0" y2="1"> + <stop offset="0%" stopColor="#2EDAA0" /> + <stop offset="100%" stopColor="#02A7D8" /> + </linearGradient> + + <filter id="pinShadow" x="-50%" y="-50%" width="200%" height="200%"> + <feDropShadow dx="0" dy="2" stdDeviation="2" floodColor="#000" floodOpacity="0.18" /> + </filter> + </defs> + + {/* pin body */} + <path + filter="url(#pinShadow)" + d="M36 2C21 2 9 14 9 29c0 22 24 50 26.4 52.7a2 2 0 0 0 3.2 0C40.9 79 63 51 63 29 63 14 51 2 36 2z" + fill="url(#pinGrad)" + /> + + {/* 내부 흰 원 */} + <circle cx="36" cy="29" r="20" fill="#FFFFFF" /> + + {label && ( + <text + x="36" + y="36" + textAnchor="middle" + fontSize="18" + fontWeight="700" + fill="#1F2937" + > + {label} + </text> + )} + </svg> + </div> + ); + } + + // 모달 열릴 때마다 초기 선택값 동기화 + useEffect(() => { + if (open) setSelected(initialSelected); + if (open) setQ(""); + }, [open, initialSelected]); + + // ESC 닫기 + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [open, onClose]); + + const filtered = useMemo(() => { + const keyword = q.trim(); + if (!keyword) return MOCK_SITES; + return MOCK_SITES.filter((s) => s.name.includes(keyword)); + }, [q]); + + const isSelected = (id: string) => selected.some((s) => s.id === id); + + const toggleSelect = (item: CandidateSite) => { + setSelected((prev) => { + const exists = prev.some((p) => p.id === item.id); + if (exists) return prev.filter((p) => p.id !== item.id); + + if (prev.length >= maxSelect) return prev; // 최대 개수 초과 방지 + return [...prev, item]; + }); + }; + + const removeChip = (id: string) => setSelected((prev) => prev.filter((p) => p.id !== id)); + + if (!open) return null; + + return ( + <div + className="fixed inset-0 z-[1000] flex items-center justify-center" + role="dialog" + aria-modal="true" + aria-label="후보지 조회" + > + {/* overlay */} + <button + type="button" + className="absolute inset-0 bg-black/30" + aria-label="모달 닫기" + onClick={onClose} + /> + + {/* panel */} + <div className="relative w-[94vw] max-w-[1700px] rounded-xl bg-white shadow-xl"> + {/* header */} + <div className="flex items-center justify-between px-8 py-6"> + <h2 className="text-2xl font-extrabold text-neutral-900">후보지 조회</h2> + <button + type="button" + aria-label="닫기" + onClick={onClose} + className="grid h-10 w-10 place-items-center rounded-sm hover:bg-neutral-100" + > + ✕ + </button> + </div> + <div className="h-px w-full bg-neutral-200" /> + + {/* body */} + <div className="px-8 py-6"> + <div className="grid grid-cols-12 gap-6"> + {/* left: search + table */} + <div className="col-span-12 lg:col-span-5"> + <div className="mb-3 text-lg font-semibold text-neutral-900">후보지 검색</div> + + <div className="relative"> + <input + value={q} + onChange={(e) => setQ(e.target.value)} + placeholder="주소검색" + className="h-12 w-full rounded-sm border border-neutral-300 bg-white px-4 pr-12 text-sm outline-none focus:border-sky-500" + /> + <button + type="button" + aria-label="검색" + onClick={() => setQ((v) => v.trim())} + className="absolute right-2 top-1/2 -translate-y-1/2 grid h-9 w-9 place-items-center rounded-full text-[#01ACE5] hover:bg-slate-100" + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> + <path + d="M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z" + stroke="currentColor" + strokeWidth="2" + /> + <path + d="M21 21l-4.3-4.3" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + /> + </svg> + </button> + </div> + + {/* table header */} + <div className="mt-4 overflow-hidden rounded-sm border border-neutral-200"> + <div className="grid grid-cols-12 bg-emerald-50/70 px-4 py-3 text-sm font-semibold text-neutral-800"> + <div className="col-span-6 flex items-center justify-center">시설명</div> + <div className="col-span-2 text-center"> + 하수처리량<br />(m³/일) + </div> + <div className="col-span-2 text-center"> + 재이용량<br />(m³/일) + </div> + <div className="col-span-2 text-center">선택</div> + </div> + + {/* rows */} + <div className="max-h-[420px] overflow-auto"> + {filtered.length === 0 ? ( + <div className="px-4 py-10 text-center text-sm text-neutral-500"> + 검색 결과가 없습니다. + </div> + ) : ( + filtered.map((s, idx) => { + const picked = isSelected(s.id); + const disabled = !picked && selected.length >= maxSelect; + return ( + <div + key={s.id} + className="grid grid-cols-12 items-center gap-2 border-t border-neutral-200 px-4 py-3 text-sm" + > + <div className="col-span-6 truncate"> + <span className="mr-2 text-neutral-500">{idx + 1}</span> + {s.name} + </div> + <div className="col-span-2 text-center">{s.sewageM3d.toLocaleString()}</div> + <div className="col-span-2 text-center">{s.reuseM3d.toLocaleString()}</div> + <div className="col-span-2 flex justify-center"> + <button + type="button" + disabled={disabled} + onClick={() => toggleSelect(s)} + className={[ + "h-8 rounded-sm px-3 text-xs", + picked ? "bg-gray-600 text-white" : "bg-sky-500 text-white hover:bg-sky-600", + disabled ? "cursor-not-allowed opacity-40" : "", + ].join(" ")} + > + {picked ? "해제" : "선택"} + </button> + </div> + </div> + ); + }) + )} + </div> + </div> + + {/* <div className="mt-2 text-xs text-neutral-500"> + 최대 {maxSelect}개까지 선택 가능합니다. + </div> */} + </div> + + {/* right: map */} + <div className="col-span-12 lg:col-span-7"> + <div className="h-[520px] w-full overflow-hidden rounded-sm border border-neutral-200 bg-neutral-50 relative"> + {/* TODO: 실제 지도 SDK 연동 시 이 영역 교체 */} + <div className="absolute inset-0 grid place-items-center text-sm text-neutral-500"> + 지도 영역(추후 지도 SDK 연동) + </div> + + {/* 선택된 항목의 "가짜 마커" */} + {selected.map((s, i) => { + const POS = [ + { top: "40%", left: "45%" }, + { top: "25%", left: "75%" }, + { top: "70%", left: "80%" }, + ] as const; + + const pos = POS[i % POS.length] ?? POS[0]; + + return ( + <div + key={s.id} + className="absolute" + style={{ top: pos.top, left: pos.left }} + > + <MapMarker label={String(i + 1)} /> + </div> + ); + })} + </div> + </div> + </div> + + {/* selected chips */} + <div className="mt-6"> + <div className="mb-2 text-lg font-semibold text-neutral-900"> + 선택된 시설 <span className="text-sm font-normal text-neutral-500">(시설명/하수처리량(m³/일)/재이용량(m³/일))</span> + </div> + + <div className="min-h-[52px] rounded-sm border border-neutral-200 bg-white px-4 py-3"> + {selected.length === 0 ? ( + <div className="text-sm text-neutral-500">선택된 시설이 없습니다.</div> + ) : ( + <div className="flex flex-wrap gap-3"> + {selected.map((s) => ( + <div + key={s.id} + className="inline-flex items-center gap-3 rounded-sm bg-[#EAF5F5] px-4 py-3 text-sm text-[#01ACE5]" + > + <span className="font-medium"> + {s.name} / {s.sewageM3d.toLocaleString()}(m³/일) / {s.reuseM3d.toLocaleString()}(m³/일) + </span> + <button + type="button" + aria-label="삭제" + onClick={() => removeChip(s.id)} + className="grid h-7 w-7 place-items-center rounded-sm hover:bg-sky-100 text-neutral-700 font-semibold" + > + ✕ + </button> + </div> + ))} + </div> + )} + </div> + </div> + + {/* footer button */} + <div className="mt-6 flex justify-center pb-2"> + <button + type="button" + onClick={() => onApply(selected)} + className="h-11 rounded-sm bg-[#0B4EA2] px-10 text-base text-white hover:opacity-90" + > + 후보지 확인 및 적용 + </button> + </div> + </div> + </div> + </div> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx b/apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx new file mode 100644 index 000000000..f997d88e1 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx @@ -0,0 +1,539 @@ +// apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx (유사사례 모달) +"use client"; + +import { useEffect, useMemo, useState, useId } from "react"; +import { createPortal } from "react-dom"; + +type Criterion = "" | "supplyAmt" | "treatment" | "usePurpose"; + +export type SimilarCaseRow = { + caseId: string; + projectName: string; + period: string; + + supplyFacilityType?: string; + supplyName?: string; + supplyCapacity?: number; + + demandName?: string; + demandAddress?: string; + + facilityCapacity?: number; + pipeKm?: number; + purpose?: string; + + // 유사기준용 + supplyAmt?: number; // 공급량(m3/일) + treatment?: string; // 처리공법 + usePurpose?: string; // 이용용도 +}; + +export type SimilarCaseDetail = { + // 보증수질 + coliform?: string; + residualCl?: string; + turbidity?: string; + bod?: string; + odor?: string; + color?: string; + tn?: string; + tp?: string; + ph?: string; + chloride?: string; + ec?: string; + note?: string; + + // 소요사업비(백만원) - 현재 타입에 있는 항목만 매핑 + costTotal?: number; + costFacility?: number; + costPipe?: number; + costDesign?: number; + costSupervision?: number; + costOther?: number; + + // 재원조달계획(백만원) + fundingTotal?: number; + fundingNational?: number; + fundingLocal?: number; + fundingSelf?: number; + fundingRate?: number; // % +}; + +function formatNumber(v: any) { + if (v === null || v === undefined || v === "" || Number.isNaN(Number(v))) return "-"; + const n = typeof v === "number" ? v : Number(String(v).replaceAll(",", "")); + if (!Number.isFinite(n)) return "-"; + return n.toLocaleString("ko-KR"); +} + +export default function SimilarCaseModal({ + open, + onClose, + rows, + detailsByCaseId, +}: { + open: boolean; + onClose: () => void; + rows: SimilarCaseRow[]; + detailsByCaseId: Record<string, SimilarCaseDetail | undefined>; +}) { + const titleId = useId(); + + const [mounted, setMounted] = useState(false); + const [criterion, setCriterion] = useState<Criterion>(""); + const [selectedCaseId, setSelectedCaseId] = useState<string>(""); + + useEffect(() => setMounted(true), []); + + // 모달 열릴 때 초기화 + useEffect(() => { + if (!open) return; + setCriterion(""); + setSelectedCaseId(""); + }, [open]); + + // ESC 닫기 + 배경 스크롤 잠금 + useEffect(() => { + if (!open) return; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prevOverflow; + }; + }, [open, onClose]); + + const groupInfo = useMemo(() => { + const counts = new Map<string, number>(); + rows.forEach((r) => counts.set(r.caseId, (counts.get(r.caseId) ?? 0) + 1)); + + const firstIndex = new Map<string, number>(); + rows.forEach((r, idx) => { + if (!firstIndex.has(r.caseId)) firstIndex.set(r.caseId, idx); + }); + + return { counts, firstIndex }; + }, [rows]); + + const selectedDetail = selectedCaseId ? detailsByCaseId[selectedCaseId] : undefined; + + // open 타입 방어 + if (typeof open !== "boolean") { + console.error("SimilarCaseModal: open prop is not boolean:", open); + return null; + } + + + if (!open || !mounted) return null; + + const tdBase = + "border border-[#C9C9C9] px-2 py-2 text-sm text-neutral-800 align-middle"; + const thBase = + "border border-[#C9C9C9] bg-[#E7F4E2] px-2 py-2 text-sm font-semibold text-neutral-800 text-center align-middle"; + + const highlightTd = "bg-neutral-200"; + + const showList = criterion !== ""; + const hasRows = rows.length > 0; + + const ui = ( + <div className="fixed inset-0 z-[1000] flex items-center justify-center"> + {/* overlay */} + <button + type="button" + className="absolute inset-0 bg-black/30" + aria-label="모달 닫기" + onClick={onClose} + /> + + {/* panel */} + <div + role="dialog" + aria-modal="true" + aria-labelledby={titleId} + className="relative w-[94vw] max-w-[1700px] rounded-xl bg-white shadow-xl" + > + {/* header */} + <div className="flex items-center justify-between px-8 py-6"> + <h2 id={titleId} className="text-2xl font-extrabold text-neutral-900"> + 유사사례 조회 + </h2> + <button + type="button" + aria-label="닫기" + onClick={onClose} + className="grid h-10 w-10 place-items-center rounded-sm hover:bg-neutral-100 focus:outline-none focus:ring-2 focus:ring-neutral-300" + > + ✕ + </button> + </div> + <div className="h-px w-full bg-neutral-200" /> + + {/* body */} + <div className="px-8 py-6"> + {/* 상단 타이틀 + 유사기준 */} + <div className="mb-4 flex items-center justify-between"> + <div className="text-lg font-semibold text-neutral-900"> + 물 재이용 사업 유사 사례 + </div> + + <div className="flex items-center gap-3"> + <span className="text-sm font-semibold text-neutral-800">유사기준</span> + <select + value={criterion} + onChange={(e) => { + const v = e.target.value as Criterion; + setCriterion(v); + setSelectedCaseId(""); + }} + className="h-10 w-[180px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option value="">유사기준선택</option> + <option value="supplyAmt">공급량</option> + <option value="treatment">처리공법</option> + <option value="usePurpose">이용용도</option> + </select> + </div> + </div> + + {/* 리스트 영역 */} + <div className="border border-[#C9C9C9] bg-white"> + {!showList ? ( + <div className="grid min-h-[260px] place-items-center bg-emerald-50/30"> + <div className="text-neutral-500">유사기준을 선택해주세요.</div> + </div> + ) : !hasRows ? ( + <div className="grid min-h-[260px] place-items-center bg-emerald-50/30"> + <div className="text-neutral-500">조회 결과가 없습니다.</div> + </div> + ) : ( + // 여기만 스크롤: rows가 4개/더 많아져서 높이를 넘으면 자동 스크롤 생성 + <div className="max-h-[300px] overflow-y-auto overflow-x-auto"> + <table className="w-full border-collapse"> + {/* thead sticky: bg + shadow + z-index로 겹침/투명 방지 */} + <thead className="sticky top-0 z-20 bg-[#EAF5F5] shadow-sm"> + <tr> + <th rowSpan={2} className={`${thBase} bg-[#EAF5F5]`}> + 선택 + </th> + <th rowSpan={2} className={`${thBase} bg-[#EAF5F5]`}> + 사업명 + </th> + <th rowSpan={2} className={`${thBase} bg-[#EAF5F5]`}> + 사업기간 + </th> + + <th colSpan={3} className={`${thBase} bg-[#EAF5F5]`}> + 공급처 + </th> + <th colSpan={2} className={`${thBase} bg-[#EAF5F5]`}> + 수요처 + </th> + <th colSpan={6} className={`${thBase} bg-[#EAF5F5]`}> + 재이용시설 + </th> + </tr> + <tr> + <th className={`${thBase} bg-[#EAF5F5]`}>시설<br/>종류</th> + <th className={`${thBase} bg-[#EAF5F5]`}>공급처명</th> + <th className={`${thBase} bg-[#EAF5F5]`}> + 시설용량 + <br /> + (m³/일) + </th> + + <th className={`${thBase} bg-[#EAF5F5]`}>수요처명</th> + <th className={`${thBase} bg-[#EAF5F5]`}>주소</th> + + <th className={`${thBase} bg-[#EAF5F5]`}> + 시설용량 + <br /> + (m³/일) + </th> + <th className={`${thBase} bg-[#EAF5F5]`}> + 관로 + <br /> + (km) + </th> + <th className={`${thBase} bg-[#EAF5F5]`}>용도</th> + + <th className={`${thBase} bg-[#EAF5F5]`}> + 공급량 + <br /> + (m³/일) + </th> + <th className={`${thBase} bg-[#EAF5F5]`}>처리공법</th> + <th className={`${thBase} bg-[#EAF5F5]`}>이용용도</th> + </tr> + </thead> + + <tbody> + {rows.map((r, idx) => { + const isFirst = groupInfo.firstIndex.get(r.caseId) === idx; + const rowSpan = groupInfo.counts.get(r.caseId) ?? 1; + const selected = selectedCaseId === r.caseId; + + const supplyAmtCls = criterion === "supplyAmt" ? highlightTd : ""; + const treatmentCls = criterion === "treatment" ? highlightTd : ""; + const usePurposeCls = criterion === "usePurpose" ? highlightTd : ""; + + return ( + <tr + key={`${r.caseId}-${idx}`} + className={[ + "cursor-pointer hover:bg-neutral-50", + selected ? "bg-sky-50" : "", + ].join(" ")} + onClick={() => setSelectedCaseId(r.caseId)} + > + {isFirst ? ( + <> + <td className={`${tdBase} text-center`} rowSpan={rowSpan}> + <input + type="checkbox" + checked={selected} + readOnly + className="h-4 w-4" + /> + </td> + <td className={`${tdBase} text-center`} rowSpan={rowSpan}> + {r.projectName ?? "-"} + </td> + <td className={`${tdBase} text-center`} rowSpan={rowSpan}> + {r.period ?? "-"} + </td> + </> + ) : null} + + <td className={`${tdBase} text-center`}>{r.supplyFacilityType ?? "-"}</td> + <td className={`${tdBase} text-center`}>{r.supplyName ?? "-"}</td> + <td className={`${tdBase} text-center`}>{formatNumber(r.supplyCapacity)}</td> + + <td className={`${tdBase} text-center`}>{r.demandName ?? "-"}</td> + <td className={`${tdBase} text-center`}>{r.demandAddress ?? "-"}</td> + + <td className={`${tdBase} text-center`}>{formatNumber(r.facilityCapacity)}</td> + <td className={`${tdBase} text-center`}>{formatNumber(r.pipeKm)}</td> + <td className={tdBase}>{r.purpose ?? "-"}</td> + + <td className={`${tdBase} text-center ${supplyAmtCls}`}> + {formatNumber(r.supplyAmt)} + </td> + <td className={`${tdBase} text-center ${treatmentCls}`}> + {r.treatment ?? "-"} + </td> + <td className={`${tdBase} text-center ${usePurposeCls}`}> + {r.usePurpose ?? "-"} + </td> + </tr> + ); + })} + </tbody> + </table> + </div> + )} + </div> + + <hr className="my-6 border-y-[1px] border-[#C9C9C9]" /> + + {/* 상세정보 */} + <div className="mt-0"> + <div className="mb-2 text-lg font-semibold text-neutral-900">상세정보</div> + + {/* 1) 보증수질 */} + <div className="overflow-hidden border border-[#C9C9C9]"> + <table className="w-full border-collapse"> + <thead> + <tr> + <th colSpan={13} className={thBase}> + 보증수질 + </th> + </tr> + <tr> + <th rowSpan={2} className={thBase}> + 총대장균군수<br />(개/100ml) + </th> + <th rowSpan={2} className={thBase}> + 용도별 결합 잔류염소<br />(mg/L) + </th> + <th rowSpan={2} className={thBase}> + 탁도<br />(NTU) + </th> + </tr> + <tr> + <th className={thBase}> + 생물학적<br />산소요구량(BOD)(mg/L) + </th> + <th className={thBase}>냄새</th> + <th className={thBase}>색도(도)</th> + <th className={thBase}>총질소<br />(T-N)(mg/L)</th> + <th className={thBase}>총인<br />(T-P)(mg/L)</th> + <th className={thBase}>수소이온<br />농도(pH)</th> + <th className={thBase}>염화물<br />(mgCL/L)</th> + <th className={thBase}>전기전도도<br />(μs/cm)</th> + <th className={thBase}>비고</th> + </tr> + </thead> + + <tbody> + <tr> + <td className={`${tdBase} text-center`}>{selectedDetail?.coliform ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.residualCl ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.turbidity ?? "-"}</td> + + <td className={`${tdBase} text-center`}>{selectedDetail?.bod ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.odor ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.color ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.tn ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.tp ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.ph ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.chloride ?? "-"}</td> + <td className={`${tdBase} text-center`}>{selectedDetail?.ec ?? "-"}</td> + + <td className={`${tdBase} text-center`}>{selectedDetail?.note ?? "-"}</td> + </tr> + </tbody> + </table> + </div> + + {/* 2) 사업비 */} + <div className="mt-4 overflow-hidden border border-[#C9C9C9]"> + <table className="w-full border-collapse"> + <thead> + <tr> + <th colSpan={14} className={thBase}> + 소요사업비(백만원) + </th> + <th colSpan={5} className={thBase}> + 재원조달계획(백만원) + </th> + <th colSpan={5} className={thBase}> + 사업비 정산(백만원) + </th> + </tr> + + <tr> + {/* 소요사업비 */} + <th rowSpan={2} className={thBase}>총</th> + <th colSpan={2} className={thBase}>토목</th> + <th rowSpan={2} className={thBase}>건축</th> + <th rowSpan={2} className={thBase}>기계</th> + <th rowSpan={2} className={thBase}>전기<br />·계측</th> + <th rowSpan={2} className={thBase}>GIS<br />DB</th> + <th rowSpan={2} className={thBase}>CCTV</th> + <th rowSpan={2} className={thBase}>시공 및 준공</th> + <th rowSpan={2} className={thBase}>폐기물</th> + <th rowSpan={2} className={thBase}>설계비</th> + <th rowSpan={2} className={thBase}>감리비</th> + <th rowSpan={2} className={thBase}>시설부대</th> + <th rowSpan={2} className={thBase}>기타</th> + + {/* 재원조달 */} + <th rowSpan={2} className={thBase}>총</th> + <th rowSpan={2} className={thBase}>국비</th> + <th rowSpan={2} className={thBase}>지방비</th> + <th rowSpan={2} className={thBase}>원인자부담</th> + <th rowSpan={2} className={thBase}>국고비율</th> + + {/* 정산 */} + <th rowSpan={2} className={thBase}>총</th> + <th rowSpan={2} className={thBase}>국비</th> + <th rowSpan={2} className={thBase}>지방비</th> + <th rowSpan={2} className={thBase}>원인자부담</th> + <th rowSpan={2} className={thBase}>국고비율</th> + </tr> + + <tr> + <th className={thBase}>재이용<br />시설</th> + <th className={thBase}>관로</th> + </tr> + </thead> + + <tbody> + <tr> + {/* 소요사업비 */} + <td className={`${tdBase} text-right`}> + {selectedDetail?.costTotal != null ? formatNumber(selectedDetail.costTotal) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.costFacility != null ? formatNumber(selectedDetail.costFacility) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.costPipe != null ? formatNumber(selectedDetail.costPipe) : "-"} + </td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.costDesign != null ? formatNumber(selectedDetail.costDesign) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.costSupervision != null ? formatNumber(selectedDetail.costSupervision) : "-"} + </td> + <td className={`${tdBase} text-center`}>-</td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.costOther != null ? formatNumber(selectedDetail.costOther) : "-"} + </td> + + {/* 재원조달 */} + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingTotal != null ? formatNumber(selectedDetail.fundingTotal) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingNational != null ? formatNumber(selectedDetail.fundingNational) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingLocal != null ? formatNumber(selectedDetail.fundingLocal) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingSelf != null ? formatNumber(selectedDetail.fundingSelf) : "-"} + </td> + <td className={`${tdBase} text-center`}> + {typeof selectedDetail?.fundingRate === "number" ? `${selectedDetail.fundingRate}%` : "-"} + </td> + + {/* 정산(필드 없으면 동일 값 표시) */} + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingTotal != null ? formatNumber(selectedDetail.fundingTotal) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingNational != null ? formatNumber(selectedDetail.fundingNational) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingLocal != null ? formatNumber(selectedDetail.fundingLocal) : "-"} + </td> + <td className={`${tdBase} text-right`}> + {selectedDetail?.fundingSelf != null ? formatNumber(selectedDetail.fundingSelf) : "-"} + </td> + <td className={`${tdBase} text-center`}> + {typeof selectedDetail?.fundingRate === "number" ? `${selectedDetail.fundingRate}%` : "-"} + </td> + </tr> + </tbody> + </table> + </div> + + {!selectedCaseId && showList ? ( + <div className="mt-3 text-sm text-neutral-500"> + 리스트에서 사업을 선택하면 상세정보가 표시됩니다. + </div> + ) : null} + </div> + </div> + </div> + </div> + ); + + return createPortal(ui, document.body); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx b/apps/dm_new/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx new file mode 100644 index 000000000..877eb7d8c --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx @@ -0,0 +1,99 @@ +// apps/dm/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx (수질기준참고 모달) +"use client"; + +import { useEffect, useId } from "react"; +import Image from "next/image"; + +type Props = { + open: boolean; + onClose: () => void; + imageSrc: string; + title?: string; +}; + +export default function WaterQualityRefModal({ + open, + onClose, + imageSrc, + title = "수질 기준 참고", +}: Props) { + const titleId = useId(); + + useEffect(() => { + if (!open) return; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + // 스크롤 잠금 + const prev = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + if (!open) return null; + + return ( + <div className="fixed inset-0 z-[120]"> + {/* Backdrop */} + <button + type="button" + aria-label="닫기" + onClick={onClose} + className="absolute inset-0 bg-black/40" + /> + + {/* Dialog */} + <div className="absolute inset-0 flex items-center justify-center p-4"> + <div + role="dialog" + aria-modal="true" + aria-labelledby={titleId} + className="w-full max-w-[1100px] rounded-[18px] bg-white shadow-2xl overflow-hidden" + > + {/* Header */} + <div className="relative px-10 pt-9 pb-6"> + <button + type="button" + onClick={onClose} + aria-label="닫기" + className="absolute right-7 top-7 h-10 w-10 grid place-items-center rounded-full hover:bg-gray-100" + > + <span className="text-2xl leading-none">×</span> + </button> + + <h2 + id={titleId} + className="text-left text-[28px] font-bold tracking-tight text-gray-900" + > + {title} + </h2> + + <div className="mt-5 h-px w-full bg-gray-200" /> + </div> + + {/* Body (스크롤 영역) */} + <div className="px-10 pb-10"> + <div className="max-h-[70vh] overflow-auto bg-white"> + {/* 이미지가 길어도 여기서 스크롤 */} + <Image + src={imageSrc} + alt="수질 기준 참고" + width={851} + height={2334} + className="block mx-auto max-w-full h-auto" + priority + /> + </div> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/apply/place/page.tsx b/apps/dm_new/app/(landing)/reuse/apply/place/page.tsx new file mode 100644 index 000000000..25591d9e6 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/place/page.tsx @@ -0,0 +1,355 @@ +// apps/dm/app/(landing)/reuse/apply/place/page.tsx +"use client"; + +import React from "react"; +import { useMemo, useState } from "react"; +import SubPageHeader from "../../_components/SubPageHeader"; +import PostcodeModal from "../../_components/PostcodeModal"; +import type { AddrItem } from "../../_components/PostcodeModal"; + +type Option = { value: string; label: string }; +type SidoValue = "" | "seoul" | "busan" | "daegu" | "incheon" | "gyeonggi"; + +const SIDO: { value: SidoValue; label: string }[] = [ + { value: "", label: "선택" }, + { value: "seoul", label: "서울특별시" }, + { value: "busan", label: "부산광역시" }, + { value: "daegu", label: "대구광역시" }, + { value: "incheon", label: "인천광역시" }, + { value: "gyeonggi", label: "경기도" }, +]; + +const SIGUNGU_MAP: Record<SidoValue, Option[]> = { + "": [{ value: "", label: "선택" }], + seoul: [ + { value: "", label: "선택" }, + { value: "gangnam", label: "강남구" }, + { value: "yeongdeungpo", label: "영등포구" }, + { value: "mapo", label: "마포구" }, + ], + busan: [ + { value: "", label: "선택" }, + { value: "haeundae", label: "해운대구" }, + { value: "suyeong", label: "수영구" }, + ], + daegu: [ + { value: "", label: "선택" }, + { value: "suseong", label: "수성구" }, + { value: "dalseo", label: "달서구" }, + ], + incheon: [ + { value: "", label: "선택" }, + { value: "namdong", label: "남동구" }, + { value: "yeonsu", label: "연수구" }, + ], + gyeonggi: [ + { value: "", label: "선택" }, + { value: "bundang", label: "성남시 분당구" }, + { value: "suji", label: "용인시 수지구" }, + { value: "ilsandong", label: "고양시 일산동구" }, + ], +}; + +/* --------------------------- + * register/page.tsx 스타일과 맞춘 공통 UI (FormRow/Divider) + * -------------------------- */ + +function Divider() { + return <div className="h-px w-full bg-neutral-200" />; +} + +function FormRow({ + label, + required, + children, +}: { + label: React.ReactNode; + required?: boolean; + children: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + {/* 라벨(좌측) */} + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6"> + <div className="flex items-center gap-1 text-lg font-semibold text-neutral-800"> + <span>{label}</span> + {required ? <span className="text-red-500">*</span> : null} + </div> + </div> + + {/* 입력(우측) */} + <div className="col-span-12 lg:col-span-10 px-6 py-6">{children}</div> + </div> + ); +} + +function TextInput({ + value, + onChange, + placeholder, + type = "text", + disabled, + className = "", +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + type?: string; + disabled?: boolean; + className?: string; +}) { + return ( + <input + type={type} + value={value} + disabled={disabled} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + className={[ + "h-11 w-full rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800", + "placeholder:text-neutral-400 focus:border-neutral-400 focus:outline-none", + disabled ? "bg-neutral-100 text-neutral-400" : "", + className, + ].join(" ")} + /> + ); +} + +function SelectBox<T extends string>({ + value, + onChange, + options, + className = "", +}: { + value: T; + onChange: (v: T) => void; + options: { value: T; label: string }[]; + className?: string; +}) { + return ( + <select + value={value} + onChange={(e) => onChange(e.target.value as T)} + className={[ + "h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800", + "focus:border-neutral-400 focus:outline-none", + className, + ].join(" ")} + > + {options.map((o) => ( + <option key={o.value} value={o.value}> + {o.label} + </option> + ))} + </select> + ); +} + +export default function DemandPlaceRegisterPage() { + /** ✅ 모달 + 주소 상태 (반드시 컴포넌트 내부!) */ + const [open, setOpen] = useState(false); + + // 모달에서 선택한 값은 이 3개에 반영 (기존 UI zip/addr1/addr2와 맞춤) + const [zip, setZip] = useState(""); + const [addr1, setAddr1] = useState(""); // 도로명 주소 넣을 곳 + const [addr2, setAddr2] = useState(""); // 상세주소(사용자 입력) + + // (필요하면 유지) 지번도 따로 보관하고 싶을 때 + // const [jibun, setJibun] = useState(""); + + const handleSelect = (it: AddrItem) => { + setZip(it.zipNo); + setAddr1(it.roadAddr); // ✅ 주소칸에 도로명 자동 입력 + // setJibun(it.jibunAddr); // ✅ 지번도 상태로 보관(원하면 UI에 표시) + setOpen(false); + }; + + /** 기존 폼 상태들 */ + const [sido, setSido] = useState<SidoValue>(""); + const [sigungu, setSigungu] = useState<string>(""); + + const [company, setCompany] = useState(""); + const [bizNo, setBizNo] = useState(""); + const [workerNo, setWorkerNo] = useState(""); + const [noWorkerNo, setNoWorkerNo] = useState(false); + + const [tel, setTel] = useState(""); + const [pw, setPw] = useState(""); + const [pw2, setPw2] = useState(""); + + // ✅ undefined 불가(Record<SidoValue,...> + sido가 SidoValue) + const sigunguOptions = useMemo<Option[]>(() => SIGUNGU_MAP[sido], [sido]); + + const onSubmit = () => { + if (!sido || !sigungu) return alert("시도/시군구를 선택해 주세요."); + if (!company) return alert("회사명을 입력해 주세요."); + if (!bizNo) return alert("사업자등록번호를 입력해 주세요."); + if (!noWorkerNo && !workerNo) + return alert("종사자등록번호를 입력하거나 '해당없음'을 선택해 주세요."); + if (!addr1) return alert("사업자주소를 입력해 주세요."); + if (!tel) return alert("전화번호를 입력해 주세요."); + if (!pw) return alert("비밀번호를 입력해 주세요."); + if (pw !== pw2) return alert("비밀번호가 일치하지 않습니다."); + + alert("수요처 등록(샘플) 완료!"); + }; + + return ( + <main className="bg-white"> + <SubPageHeader + title="수요처등록" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: "수요처등록" }, + ]} + /> + + {/* register/page.tsx와 동일한 테이블 룩앤필 */} + <div className="border border-neutral-200 bg-white"> + <FormRow label="시도" required> + <SelectBox<SidoValue> + value={sido} + onChange={(v) => { + setSido(v); + setSigungu(""); // 시도 변경 시 초기화 + }} + options={SIDO} + /> + </FormRow> + + <Divider /> + + <FormRow label="시군구" required> + <select + value={sigungu} + onChange={(e) => setSigungu(e.target.value)} + className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none" + > + {sigunguOptions.map((o) => ( + <option key={o.value} value={o.value}> + {o.label} + </option> + ))} + </select> + </FormRow> + + <Divider /> + + <FormRow label="회사명" required> + <div className="max-w-[420px]"> + <TextInput value={company} onChange={setCompany} placeholder="회사명을 입력해주세요." /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="사업자등록번호" required> + <div className="max-w-[420px]"> + <TextInput value={bizNo} onChange={setBizNo} placeholder="사업자 등록번호를 입력해주세요." /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="종사자등록번호"> + <div className="flex flex-wrap items-center gap-4"> + <div className="w-[420px] max-w-full"> + <TextInput + value={workerNo} + onChange={setWorkerNo} + placeholder="종사자등록번호를 입력해주세요." + disabled={noWorkerNo} + /> + </div> + + <label className="flex items-center gap-2 text-sm text-neutral-700"> + <input + type="checkbox" + checked={noWorkerNo} + onChange={(e) => { + setNoWorkerNo(e.target.checked); + if (e.target.checked) setWorkerNo(""); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 해당없음 + </label> + </div> + </FormRow> + + <Divider /> + + <FormRow label="사업자주소" required> + <div className="max-w-[420px] space-y-2"> + <div className="flex gap-2"> + <div className="w-[180px]"> + <TextInput value={zip} onChange={setZip} placeholder="우편번호" /> + </div> + + <button + type="button" + onClick={() => setOpen(true)} + className="h-11 rounded-md bg-[#888] px-4 text-sm text-white hover:brightness-95" + > + 우편번호 검색 + </button> + </div> + + {/* ✅ 선택된 도로명주소가 자동으로 들어감 */} + <TextInput value={addr1} onChange={setAddr1} placeholder="주소" /> + + {/* ✅ 상세주소는 사용자 입력 */} + <TextInput value={addr2} onChange={setAddr2} placeholder="상세 주소를 입력하세요." /> + + {/* (선택) 지번주소도 보여주고 싶으면 */} + {/* {jibun ? <p className="text-xs text-neutral-500">지번: {jibun}</p> : null} */} + </div> + </FormRow> + + <Divider /> + + <FormRow label="전화번호" required> + <div className="max-w-[420px]"> + <TextInput value={tel} onChange={setTel} placeholder="전화번호를 입력하세요." /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="비밀번호" required> + <div className="max-w-[420px]"> + <TextInput value={pw} onChange={setPw} placeholder="비밀번호를 입력하세요." type="password" /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="비밀번호 확인" required> + <div className="max-w-[420px]"> + <TextInput + value={pw2} + onChange={setPw2} + placeholder="비밀번호를 한번 더 입력하세요." + type="password" + /> + </div> + </FormRow> + </div> + + {/* register/page.tsx와 동일하게 하단 중앙 버튼 */} + <div className="flex justify-center py-12"> + <button + type="button" + onClick={onSubmit} + className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90" + > + 수요처 등록 + </button> + </div> + + {/* ✅ 모달 */} + <PostcodeModal open={open} onClose={() => setOpen(false)} onSelect={handleSelect} /> + </main> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/apply/register/page.tsx b/apps/dm_new/app/(landing)/reuse/apply/register/page.tsx new file mode 100644 index 000000000..2a9bbaf9a --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/register/page.tsx @@ -0,0 +1,818 @@ +// apps/dm/app/(landing)/reuse/apply/register/page.tsx (수요정보등록 페이지) +"use client"; + +import { useMemo, useState, useRef } from "react"; +import SubPageHeader from "../../_components/SubPageHeader"; +import WaterQualityRefModal from "../modal/WaterQualityRefModal"; +import CandidateSiteModal, { type CandidateSite } from "../modal/CandidateSiteModal"; +import SimilarCaseModal, { type SimilarCaseRow, type SimilarCaseDetail } from "../modal/SimilarCaseModal"; + +type PHRange = { min: string; max: string }; + +export default function DemandRegisterPage() { + const today = useMemo(() => { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; + }, []); + + const [openRef, setOpenRef] = useState(false); + + const [openCand, setOpenCand] = useState(false); + const [selectedCands, setSelectedCands] = useState<CandidateSite[]>([]); + + // 유사사례 모달 + const [openSimilar, setOpenSimilar] = useState(false); + + // 유사사례 rows (목업) - API 연동 시 여기만 교체 + // const rows: SimilarCaseRow[] = useMemo( + // () => [ + // { + // caseId: "C-001", + // projectName: "OO시 물 재이용(빗물, 중수도) 시설 설치사업", + // period: "2020.01. ~ 2025.05.", + // supplyFacilityType: "중수도", + // supplyName: "OO새교복지타운", + // supplyCapacity: 0, + // demandName: "OO새교복지타운", + // demandAddress: "OO시 수청로 192", + // facilityCapacity: 50, + // pipeKm: 0, + // purpose: "청소, 화장실, 조경", + // supplyAmt: 50, + // treatment: "MBLM", + // usePurpose: "청소,화장실", + // }, + // { + // caseId: "C-001", + // projectName: "OO시 물 재이용(빗물, 중수도) 시설 설치사업", + // period: "2020.01. ~ 2025.05.", + // supplyFacilityType: "중수도", + // supplyName: "OO스포츠센터", + // supplyCapacity: 0, + // demandName: "OO스포츠센터", + // demandAddress: "OO시 경기동로 33", + // facilityCapacity: 50, + // pipeKm: 0, + // purpose: "청소, 화장실, 조경", + // supplyAmt: 50, + // treatment: "MBLM", + // usePurpose: "청소,화장실", + // }, + // { + // caseId: "C-002", + // projectName: "OO 엄성저수지 하수처리수 재이용사업", + // period: "2019.12 ~ 2026.01", + // supplyFacilityType: "하수", + // supplyName: "OO 3,4단계 공공하수처리시설", + // supplyCapacity: 70000, + // demandName: "OO천", + // demandAddress: "-", + // facilityCapacity: 0, + // pipeKm: 3, + // purpose: "하천 등 유지용수", + // supplyAmt: 2300, + // treatment: "탈황,총인, 생물산화과,소독", + // usePurpose: "하천 등 유지용수", + // }, + // { + // caseId: "C-002", + // projectName: "OO 엄성저수지 하수처리수 재이용사업", + // period: "2019.12 ~ 2026.01", + // supplyFacilityType: "하수", + // supplyName: "OO 3,4단계 공공하수처리시설", + // supplyCapacity: 70000, + // demandName: "OO저수지", + // demandAddress: "-", + // facilityCapacity: 0, + // pipeKm: 5, + // purpose: "하천 등 유지용수", + // supplyAmt: 7700, + // treatment: "탈황,총인, 생물산화과,소독", + // usePurpose: "하천 등 유지용수", + // }, + // ], + // [] + // ); + + // 유사사례 상세(목업) - caseId로 매핑 + // const detailsByCaseId: Record<string, SimilarCaseDetail> = useMemo( + // () => ({ + // "C-001": { + // coliform: "불검출", + // residualCl: "0.2이상", + // turbidity: "1.5이하", + // bod: "4.0이하", + // odor: "불쾌하지않을것", + // color: "10이하", + // tn: "-", + // tp: "-", + // ph: "5.8~8.5", + // chloride: "-", + // ec: "-", + // note: "-", + + // costTotal: 4305, + // costFacility: 3518, + // costPipe: undefined, + // costDesign: 350, + // costSupervision: 417, + // costOther: 12, + + // fundingTotal: 4305, + // fundingNational: 3104, + // fundingLocal: 1291, + // fundingSelf: undefined, + // fundingRate: 70.0, + // }, + // "C-002": { + // coliform: "불검출", + // residualCl: "0.15이상", + // turbidity: "2.0이하", + // bod: "5.0이하", + // odor: "무", + // color: "10이하", + // tn: "-", + // tp: "-", + // ph: "6.0~8.5", + // chloride: "-", + // ec: "-", + // note: "-", + + // costTotal: 9800, + // costFacility: 2100, + // costPipe: 1800, + // costDesign: 300, + // costSupervision: 280, + // costOther: 1200, + + // fundingTotal: 9800, + // fundingNational: 4900, + // fundingLocal: 3000, + // fundingSelf: 1900, + // fundingRate: 50, + // }, + // }), + // [] + // ); + + // 기본정보 + const [reqDate, setReqDate] = useState<string>(today); + const [reqQty, setReqQty] = useState<string>(""); + const [usePurpose, setUsePurpose] = useState<string>(""); + const [hopePrice, setHopePrice] = useState<string>(""); + + // 요구수질 + const [coliform, setColiform] = useState<string>(""); + const [noDetect, setNoDetect] = useState<boolean>(false); + + const [tn, setTn] = useState<string>(""); + const [residualCl, setResidualCl] = useState<string>(""); + const [tp, setTp] = useState<string>(""); + const [turbidity, setTurbidity] = useState<string>(""); + const [ph, setPh] = useState<PHRange>({ min: "", max: "" }); + const [bod, setBod] = useState<string>(""); + const [chloride, setChloride] = useState<string>(""); + const [odor, setOdor] = useState<string>("불쾌하지 않을 것"); + const [ec, setEc] = useState<string>(""); + const [color, setColor] = useState<string>(""); + const [remark, setRemark] = useState<string>(""); + + // 공급처 후보지 선택(필수아님) + const [cand1, setCand1] = useState(""); + const [cand2, setCand2] = useState(""); + const [cand3, setCand3] = useState(""); + + // 샘플 데이터 + const similarRows: SimilarCaseRow[] = [ + // ---------------- A (2행) ---------------- + { + caseId: "A", + projectName: "OO시 물 재이용(빗물, 중수도) 시설 설치사업", + period: "2020.01.~2025.05.", + supplyFacilityType: "중수도", + supplyName: "OO폐수재이용", + supplyCapacity: 50, + + demandName: "OO시체육센터", + demandAddress: "OO시 수성로 192", + + facilityCapacity: 50, + pipeKm: 3, + purpose: "조경", + supplyAmt: 50, + treatment: "MBLM", + usePurpose: "청소, 화장실", + }, + // A의 공급처 row 추가(=표 1행 추가) → 나머지 컬럼도 같이 채움 + { + caseId: "A", + projectName: "OO시 물 재이용(빗물, 중수도) 시설 설치사업", + period: "2020.01.~2025.05.", + supplyFacilityType: "중수도", + supplyName: "OO스포츠센터", + supplyCapacity: 30, + + demandName: "OO시체육센터", + demandAddress: "OO시 수성로 192", + + facilityCapacity: 50, + pipeKm: 3, + purpose: "조경", + supplyAmt: 50, + treatment: "MBLM", + usePurpose: "청소, 화장실", + }, + + // ---------------- B (2행) ---------------- + { + caseId: "B", + projectName: "OO 염성저수지 하수처리수 재이용사업", + period: "2019.12~2026.1", + supplyFacilityType: "하수", + supplyName: "OO3.4단계 공공하수처리시설", + supplyCapacity: 70000, + + demandName: "OO천", + demandAddress: "-", + + facilityCapacity: 0, + pipeKm: 3, + purpose: "하천 등 유지용수", + supplyAmt: 2300, + treatment: "탈황, 총인, 생물산화, 소독", + usePurpose: "하천 등 유지용수", + }, + // B의 공급처 row 추가(=표 1행 추가) + { + caseId: "B", + projectName: "OO 염성저수지 하수처리수 재이용사업", + period: "2019.12~2026.1", + supplyFacilityType: "하수", + supplyName: "OO3.4단계 공공하수처리시설(2라인)", + supplyCapacity: 70000, + + demandName: "OO저수지", + demandAddress: "-", + + facilityCapacity: 0, + pipeKm: 5, + purpose: "하천 등 유지용수", + supplyAmt: 7700, + treatment: "탈황, 총인, 생물산화, 소독", + usePurpose: "하천 등 유지용수", + }, + + // ---------------- C (1행) ---------------- + { + caseId: "C", + projectName: "OO 산업단지 공업용수 재이용사업", + period: "2021.03~2024.12", + supplyFacilityType: "하수", + supplyName: "OO공공하수처리장", + supplyCapacity: 15000, + + demandName: "OO산업단지", + demandAddress: "OO시 산업로 25", + + facilityCapacity: 5000, + pipeKm: 4, + purpose: "공업용수", + supplyAmt: 5000, + treatment: "UF + RO", + usePurpose: "공업용수", + }, + + // ---------------- D (1행) ---------------- + { + caseId: "D", + projectName: "OO 공원 조경용수 재이용사업", + period: "2018.05~2022.11", + supplyFacilityType: "중수도", + supplyName: "OO정수장", + supplyCapacity: 8000, + + demandName: "OO중앙공원", + demandAddress: "OO시 공원로 88", + + facilityCapacity: 1200, + pipeKm: 2, + purpose: "조경용수", + supplyAmt: 1200, + treatment: "모래여과", + usePurpose: "조경", + }, +]; + + const similarDetailsByCaseId: Record<string, SimilarCaseDetail> = { + A: { + coliform: "불검출", + residualCl: "0.2이상", + turbidity: "1.5이하", + bod: "4.0이하", + odor: "불쾌하지않을것", + color: "10이하", + tn: "-", + tp: "-", + ph: "5.8~8.5", + chloride: "-", + ec: "-", + note: "-", + costTotal: 4305, + costFacility: 3518, + costPipe: 0, + costDesign: 350, + costSupervision: 417, + costOther: 12, + fundingTotal: 4305, + fundingNational: 3104, + fundingLocal: 1291, + fundingSelf: 0, + fundingRate: 70.0, + }, + B: { + coliform: "-", + residualCl: "-", + turbidity: "-", + bod: "-", + odor: "-", + color: "-", + tn: "-", + tp: "-", + ph: "-", + chloride: "-", + ec: "-", + note: "-", + costTotal: 0, + costFacility: 0, + costPipe: 0, + costDesign: 0, + costSupervision: 0, + costOther: 0, + fundingTotal: 0, + fundingNational: 0, + fundingLocal: 0, + fundingSelf: 0, + fundingRate: 0, + }, + C: { + coliform: "불검출", + residualCl: "0.2이상", + turbidity: "1.5이하", + bod: "4.0이하", + odor: "불쾌하지않을것", + color: "10이하", + tn: "-", + tp: "-", + ph: "5.8~8.5", + chloride: "-", + ec: "-", + note: "-", + costTotal: 4305, + costFacility: 3518, + costPipe: 0, + costDesign: 350, + costSupervision: 417, + costOther: 12, + fundingTotal: 4305, + fundingNational: 3104, + fundingLocal: 1291, + fundingSelf: 0, + fundingRate: 70.0, + }, + D: { + coliform: "-", + residualCl: "-", + turbidity: "-", + bod: "-", + odor: "-", + color: "-", + tn: "-", + tp: "-", + ph: "-", + chloride: "-", + ec: "-", + note: "-", + costTotal: 0, + costFacility: 0, + costPipe: 0, + costDesign: 0, + costSupervision: 0, + costOther: 0, + fundingTotal: 0, + fundingNational: 0, + fundingLocal: 0, + fundingSelf: 0, + fundingRate: 0, + }, + }; + + return ( + <> + <main className="bg-white"> + <SubPageHeader + title="수요정보등록" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: "수요정보등록" }, + ]} + /> + + <div> + <div className="border border-neutral-200 bg-white"> + <FormRow label="요구시기" required> + <DateInput value={reqDate} onChange={setReqDate} /> + </FormRow> + <Divider /> + + <FormRow label="요구수량(m²/d)" required> + <TextInput value={reqQty} onChange={setReqQty} /> + </FormRow> + <Divider /> + + <FormRow label="사용용도" required> + <TextInput value={usePurpose} onChange={setUsePurpose} /> + </FormRow> + <Divider /> + + <FormRow label="희망단가(원)" required> + <TextInput value={hopePrice} onChange={setHopePrice} /> + </FormRow> + </div> + + <div className="grid grid-cols-12"> + <div className="col-span-12 lg:col-span-2 inline-flex items-center justify-center gap-3 px-6 py-6 text-center"> + <span className="text-lg font-semibold text-neutral-800">요구수질</span> + <button + type="button" + onClick={() => setOpenRef(true)} + className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50" + > + 수질기준 참고 + </button> + + <WaterQualityRefModal + open={openRef} + onClose={() => setOpenRef(false)} + imageSrc="/images/landing/water_quality_ref_1.png" + /> + </div> + </div> + + <div className="border border-neutral-200 bg-white"> + <QualityRow + leftLabel="총대장균군수(개/mg)" + leftRequired + leftControl={ + <div className="flex items-center gap-4"> + <TextInput + value={coliform} + onChange={setColiform} + disabled={noDetect} + className={noDetect ? "bg-neutral-100 text-neutral-400" : ""} + /> + <label className="flex items-center gap-2 text-sm text-neutral-700"> + <input + type="checkbox" + checked={noDetect} + onChange={(e) => { + setNoDetect(e.target.checked); + if (e.target.checked) setColiform(""); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 불검출 + </label> + </div> + } + rightLabel="총질소(T-N)(mg/L)" + rightControl={<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />} + /> + <Divider /> + + <QualityRow + leftLabel="결합잔류염소(mg/L)" + leftRequired + leftControl={<TextInput value={residualCl} onChange={setResidualCl} />} + rightLabel="총인(T-P)(mg/L)" + rightControl={<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />} + /> + <Divider /> + + <QualityRow + leftLabel="탁도(NTU)" + leftRequired + leftControl={<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />} + rightLabel="수소이온농도(pH)" + rightControl={ + <div className="flex items-center gap-2"> + <TextInput value={ph.min} onChange={(v) => setPh((p) => ({ ...p, min: v }))} className="w-32" /> + <span className="text-neutral-500">~</span> + <TextInput value={ph.max} onChange={(v) => setPh((p) => ({ ...p, max: v }))} className="w-32" /> + </div> + } + /> + <Divider /> + + <QualityRow + leftLabel={ + <> + 생물화학적산소요구량 + <br /> + (BOD)(mg/L) + </> + } + leftRequired + leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />} + rightLabel="염화물(mgCL/L)" + rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />} + /> + <Divider /> + + <QualityRow + leftLabel="냄새" + leftRequired + leftControl={ + <select + value={odor} + onChange={(e) => setOdor(e.target.value)} + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option>불쾌하지 않을 것</option> + <option>약간 있음</option> + <option>있음</option> + </select> + } + rightLabel="전기전도도(µs/cm)" + rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix="이하" />} + /> + <Divider /> + + <QualityRow + leftLabel="색도(도)" + leftRequired + leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />} + rightLabel="비고" + rightControl={ + <textarea + value={remark} + onChange={(e) => setRemark(e.target.value)} + className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500" + /> + } + /> + <Divider /> + + <div className="grid grid-cols-12"> + <LabelCell> + 공급처 후보지 선택 + <br /> + (필수아님) + </LabelCell> + + <div className="col-span-12 md:col-span-10 p-6"> + <div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4"> + <TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" /> + <TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" /> + <TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" /> + + <div className="flex items-center gap-3 xl:ml-2"> + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700" + onClick={() => setOpenCand(true)} + > + 후보지 조회 + </button> + + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700" + onClick={() => setOpenSimilar(true)} + > + 유사사례 조회 + </button> + </div> + </div> + </div> + </div> + </div> + + <div className="flex justify-center py-12"> + <button + type="button" + className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90" + onClick={() => console.log("수요정보 등록")} + > + 수요정보 등록 + </button> + </div> + </div> + </main> + + {/* 후보지 조회 모달 */} + <CandidateSiteModal + open={openCand} + onClose={() => setOpenCand(false)} + initialSelected={selectedCands} + onApply={(list) => { + setSelectedCands(list); + setCand1(list[0]?.name ?? ""); + setCand2(list[1]?.name ?? ""); + setCand3(list[2]?.name ?? ""); + setOpenCand(false); + }} + /> + + {/* 유사사례 모달 */} + <SimilarCaseModal + open={openSimilar} + onClose={() => setOpenSimilar(false)} + rows={similarRows} + detailsByCaseId={similarDetailsByCaseId} + /> + </> + ); +} + +/* ----------------------------- UI helpers ----------------------------- */ + +function Divider() { + return <div className="h-px w-full bg-neutral-200" />; +} + +function LabelCell({ + children, + required, +}: { + children: React.ReactNode; + required?: boolean; +}) { + return ( + <div className="col-span-12 md:col-span-2 flex items-center justify-center bg-emerald-50/70 px-6 py-6 text-center text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + {children} + {required ? <span className="text-red-500">*</span> : null} + </span> + </div> + ); +} + +function FormRow({ + label, + required, + children, +}: { + label: string; + required?: boolean; + children: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + <LabelCell required={required}>{label}</LabelCell> + <div className="col-span-12 md:col-span-10 p-6">{children}</div> + </div> + ); +} + +function QualityRow({ + leftLabel, + leftRequired, + leftControl, + rightLabel, + rightControl, +}: { + leftLabel: React.ReactNode; + leftRequired?: boolean; + leftControl: React.ReactNode; + rightLabel: React.ReactNode; + rightControl: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + <div className="col-span-12 lg:col-span-2 flex items-center justify-center bg-emerald-50/70 px-6 py-6 text-center text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + <span className="leading-6">{leftLabel}</span> + {leftRequired ? <span className="text-red-500">*</span> : null} + </span> + </div> + + <div className="col-span-12 lg:col-span-4 p-6">{leftControl}</div> + + <div className="col-span-12 lg:col-span-2 flex items-center justify-center bg-emerald-50/70 px-6 py-6 text-center text-lg font-semibold text-neutral-800"> + <span className="leading-6">{rightLabel}</span> + </div> + + <div className="col-span-12 lg:col-span-4 p-6">{rightControl}</div> + </div> + ); +} + +function TextInput({ + value, + onChange, + placeholder, + disabled, + className, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +}) { + return ( + <input + value={value} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className={[ + "h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none", + "focus:border-neutral-500", + disabled ? "cursor-not-allowed" : "", + className ?? "", + ].join(" ")} + /> + ); +} + +function InlineSuffixInput({ + value, + onChange, + suffix, +}: { + value: string; + onChange: (v: string) => void; + suffix: string; +}) { + return ( + <div className="flex items-center gap-3"> + <TextInput value={value} onChange={onChange} className="max-w-[200px]" /> + <span className="text-sm text-neutral-600">{suffix}</span> + </div> + ); +} + +function DateInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + const ref = useRef<HTMLInputElement | null>(null); + + const openPicker = () => { + const el = ref.current; + if (!el) return; + + if (typeof (el as any).showPicker === "function") { + (el as any).showPicker(); + return; + } + + el.focus(); + el.click(); + }; + + return ( + <div className="relative w-full max-w-[200px]"> + <input + ref={ref} + type="date" + value={value} + onChange={(e) => onChange(e.target.value)} + className={[ + "h-11 w-full rounded-md border border-neutral-300 bg-white px-3 pr-10 text-sm outline-none focus:border-neutral-500", + "[&::-webkit-calendar-picker-indicator]:opacity-0", + "[&::-webkit-calendar-picker-indicator]:absolute", + "[&::-webkit-calendar-picker-indicator]:right-0", + "[&::-webkit-calendar-picker-indicator]:w-10", + "[&::-webkit-calendar-picker-indicator]:h-full", + "[&::-webkit-calendar-picker-indicator]:cursor-pointer", + ].join(" ")} + /> + + <button + type="button" + onClick={openPicker} + className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-neutral-500 hover:text-neutral-700" + aria-label="달력 열기" + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> + <path + d="M7.14286 7.99104H16.7812M6.48512 2.5625V4.19126M17.3125 2.5625V4.19106M20.5 7.19106L20.5 18.5625C20.5 20.2194 19.1569 21.5625 17.5 21.5625H6.5C4.84315 21.5625 3.5 20.2194 3.5 18.5625V7.19106C3.5 5.53421 4.84315 4.19106 6.5 4.19106H17.5C19.1569 4.19106 20.5 5.53421 20.5 7.19106Z" + stroke="#1A1A1A" + strokeWidth={2} + strokeLinecap="round" + strokeLinejoin="round" + /> + </svg> + </button> + </div> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/apply/status/page.tsx b/apps/dm_new/app/(landing)/reuse/apply/status/page.tsx new file mode 100644 index 000000000..b69b0fdd9 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/apply/status/page.tsx @@ -0,0 +1,369 @@ +// apps/dm/app/(landing)/reuse/apply/status/page.tsx +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import SubPageHeader from "../../_components/SubPageHeader"; + +type WaterDetail = { + toc: string; + bdo: string; + tn: string; + tp: string; + cl: string; + conductivity: string; + coliform: string; + turbidity: string; + odor: string; + color: string; + ph: string; + note: string; +}; + +type Row = { + id: number; + sido: string; + sigungu: string; + company: string; + address: string; + phone: string; + date: string; + status: "진행중" | "진행완료"; + detail: { + reqDate: string; + reqQty: string; + usePurpose: string; + price: string; + water: WaterDetail; + candidates: [string, string, string]; + }; +}; + +const MOCK: Row[] = [ + { + id: 1, + sido: "서울특별시", + sigungu: "금천구", + company: "서울워터솔루션(주)", + address: "서울특별시 금천구 가산디지털2로 119, 24층(가산동)", + phone: "010-0000-0000", + date: "2025-01-14", + status: "진행중", + detail: { + reqDate: "2025-02", + reqQty: "1,200", + usePurpose: "공업용수", + price: "350", + water: { + toc: "4", + bdo: "3", + tn: "10", + tp: "1.0", + cl: "120", + conductivity: "800", + coliform: "0", + turbidity: "2", + odor: "무", + color: "무", + ph: "7.2", + note: "-", + }, + candidates: ["후보지1", "후보지2", "후보지3"], + }, + }, + { + id: 2, + sido: "제주특별자치도", + sigungu: "서귀포", + company: "회사명이 길어도괜찮지", + address: "제주특별자치도 서귀포시 천제연로 156, 3층 2호(색달동 1077호)", + phone: "010-0000-0000", + date: "2025-01-14", + status: "진행완료", + detail: { + reqDate: "2025-03", + reqQty: "800", + usePurpose: "조경용수", + price: "300", + water: { + toc: "5", + bdo: "4", + tn: "12", + tp: "1.2", + cl: "140", + conductivity: "900", + coliform: "0", + turbidity: "3", + odor: "무", + color: "무", + ph: "7.0", + note: "현장 협의", + }, + candidates: ["후보지1", "후보지2", "후보지3"], + }, + }, +]; + +function StatusBadge({ v }: { v: Row["status"] }) { + const cls = v === "진행중" ? "text-red-600" : "text-lime-600"; + return <span className={["font-medium", cls].join(" ")}>{v}</span>; +} + +type TopField = { label: string; value: string; required?: boolean }; +type PairField = { label: string; value: string }; + +export default function DemandStatusPage() { + const rows = useMemo(() => MOCK, []); + + // ✅ rows가 빈 경우도 대비 (런타임 오류 방지) + const [selectedId, setSelectedId] = useState<number | null>(rows[0]?.id ?? null); + + // ✅ 상세영역 스크롤 이동용 + const detailRef = useRef<HTMLDivElement | null>(null); + + // ✅ selected 계산 안정화 + const selected = useMemo(() => { + if (!rows.length) return null; + const found = rows.find((r) => r.id === selectedId); + return found ?? rows[0]; + }, [rows, selectedId]); + + // ✅ rows가 바뀌었는데 selectedId가 null이면 자동 첫번째 선택 + useEffect(() => { + const first = rows[0]; + if (!first) return; // ✅ rows가 비어있으면 종료 (TS + 런타임 안전) + + if (selectedId == null) setSelectedId(first.id); + }, [rows, selectedId]); + + // ✅ 상단 2행(요구시기/요구수량/사용용도/희망단가) - TS 안전하게 object로 구성 + const topFields: TopField[] = useMemo(() => { + if (!selected) return []; + return [ + { label: "요구시기", value: selected.detail.reqDate, required: true }, + { label: "요구수량(㎡/d)", value: selected.detail.reqQty }, + { label: "사용용도", value: selected.detail.usePurpose }, + { label: "희망단가(원)", value: selected.detail.price }, + ]; + }, [selected]); + + // ✅ 수질 항목 - TS 안전 + const waterFields: PairField[] = useMemo(() => { + if (!selected) return []; + const w = selected.detail.water; + return [ + { label: "총대장균군수(개/100mℓ)", value: w.coliform }, + { label: "결합잔류염소(mg/L)", value: "-" }, + { label: "탁도(NTU)", value: w.turbidity }, + + { label: "생물화학적산소요구량(BOD)(mg/L)", value: w.bdo }, + { label: "냄새", value: w.odor }, + { label: "색도(도)", value: w.color }, + + { label: "총질소(T-N)(mg/L)", value: w.tn }, + { label: "총인(T-P)(mg/L)", value: w.tp }, + { label: "수소이온농도(pH)", value: w.ph }, + + { label: "염화물(mgCL/L)", value: w.cl }, + { label: "전기전도도(µs/cm)", value: w.conductivity }, + { label: "비고", value: w.note }, + ]; + }, [selected]); + + const onSelectRow = (id: number) => { + setSelectedId(id); + + // ✅ “행 클릭 → 아래 상세로 보여짐” 체감 확실히: 상세영역으로 이동 + // (사용자가 이미 아래를 보고 있다면 거의 움직이지 않음) + requestAnimationFrame(() => { + detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + }; + + return ( + <main className="bg-white"> + {/* 서브 타이틀 + 브레드크럼 */} + <SubPageHeader + title="수요신청현황" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: "수요신청현황" }, + ]} + hideFooterLine + hideGuideText + /> + + {/* 컨텐츠 영역 */} + <div> + {/* 리스트 테이블 */} + <section className="mt-6"> + <div className="text-2xl font-bold text-slate-700">수요정보 신청현황</div> + + <div className="mt-3 border border-neutral-200"> + <table className="w-full table-fixed"> + <thead className="bg-[#E7F4E2]"> + <tr className="text-sm text-slate-700"> + <th className="w-[60px] px-3 py-3 text-center">NO.</th> + <th className="w-[120px] px-3 py-3 text-center">시도</th> + <th className="w-[120px] px-3 py-3 text-center">시군구</th> + <th className="w-[180px] px-3 py-3 text-center">회사명</th> + <th className="px-3 py-3 text-center">공장주소</th> + <th className="w-[140px] px-3 py-3 text-center">전화번호</th> + <th className="w-[120px] px-3 py-3 text-center">신청일자</th> + <th className="w-[110px] px-3 py-3 text-center">진행상태</th> + </tr> + </thead> + + <tbody className="bg-white text-base text-slate-700"> + {rows.length === 0 ? ( + <tr> + <td colSpan={8} className="px-3 py-10 text-center text-slate-400"> + 조회된 신청내역이 없습니다. + </td> + </tr> + ) : ( + rows.map((r) => { + const active = r.id === selected?.id; + return ( + <tr + key={r.id} + role="row" + tabIndex={0} + onClick={() => onSelectRow(r.id)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") onSelectRow(r.id); + }} + className={[ + "border-t border-neutral-200 outline-none", + "cursor-pointer", + active ? "bg-slate-50" : "hover:bg-slate-50", + "focus-visible:ring-2 focus-visible:ring-slate-300", + ].join(" ")} + aria-selected={active} + > + <td className="px-3 py-3 text-center">{r.id}</td> + <td className="px-3 py-3 text-center">{r.sido}</td> + <td className="px-3 py-3 text-center">{r.sigungu}</td> + <td className="px-3 py-3 text-center">{r.company}</td> + <td className="px-3 py-3 text-center">{r.address}</td> + <td className="px-3 py-3 text-center">{r.phone}</td> + <td className="px-3 py-3 text-center">{r.date}</td> + <td className="px-3 py-3 text-center"> + <StatusBadge v={r.status} /> + </td> + </tr> + ); + }) + )} + </tbody> + </table> + </div> + </section> + + {/* 상세현황 */} + <section className="mt-10 pb-16" ref={detailRef}> + + <div className="flex justify-between items-center"> + <div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div> + <button + type="button" + className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + > + 수정 + </button> + </div> + + {!selected ? ( + <div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400"> + 상세 정보를 표시할 신청건이 없습니다. + </div> + ) : ( + <div className="mt-3 border border-neutral-200"> + <div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수량 + </div> + + <div className="grid grid-cols-12 text-sm"> + {/* 상단 2행 */} + {topFields.map((f) => ( + <div + key={f.label} + className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {f.label} + {f.required ? <span className="ml-1 text-red-500">*</span> : null} + </div> + <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center"> + {f.value} + </div> + </div> + ))} + + {/* 수질 */} + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수질 + </div> + + {waterFields.map((f) => ( + <div + key={f.label} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {f.label} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {f.value} + </div> + </div> + ))} + + {/* 공급처 후보지 */} + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 공급처 후보지 + </div> + + {selected.detail.candidates.map((v, i) => ( + <div + key={`candidate-${i}`} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 후보지{i + 1} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {v} + </div> + </div> + ))} + + {/* 수요신청 진행상태 */} + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200"> + 수요신청 진행상태 + </div> + + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 진행상태 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {selected.status} + </div> + </div> + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 공급처매칭결과 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + + </div> + </div> + </div> + </div> + )} + </section> + </div> + </main> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/australia/page.tsx b/apps/dm_new/app/(landing)/reuse/business/globalCase/australia/page.tsx new file mode 100644 index 000000000..fa9eaad72 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/globalCase/australia/page.tsx @@ -0,0 +1,151 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/australia/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "호주", href: "/reuse/business/globalCase/australia" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/australia_01.png", alt: "호주 사례 1" }, + ]} + /> + + {/* 생활용수로 재이용수의 적극 활용 */} + <section className="mt-8"> + <SectionTitle>생활용수로 재이용수의 적극 활용</SectionTitle> + <div className="px-4 mt-6"> + <ul className="space-y-4 text-base text-[#4F4F4F]"> + <li> + – 재이용수를 공원, 골프장 등의 관개용수로 공급(초록색 지역) + </li> + <li> + – Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역) + </li> + <li> + – 현재 7,000여 가구가 재이용수를 사용 + </li> + <li> + – 2030년까지 43,000가구로 확대 예정 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 호주 재이용수 광역 활용(퀸즈랜드주 브리즈번) */} + <section className="mt-6"> + <SectionTitle>호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망 + </li> + <li> + • 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급<br /> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li> + <li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li> + </ul> + </li> + <li> + • 12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개, 저수지 28개와 600km 이상의 물 공급 관로를 건설 + </li> + <li> + • 지역적으로 강수량이 고르지 않아, 댐의 저수율이 불균등할 때 지역의 물 공급을 보충하는 중요한 역할을 수행 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여 심각한 물 부족 현상이 발생함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구 증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가 필요함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에 대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고 토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용 활성화를 위한 Water Smart Australia 프로그램을 시행</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의 특성과 주 정부의 정책 방향에 따라 주마다도 많은 차이가 있음. 특히 대부분의 주에서 수 자원 개발 기능과 수도 공급의 기능을 분리하여 주정부<br/>산하 기관에서 운영하는게 특징</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/eu/page.tsx b/apps/dm_new/app/(landing)/reuse/business/globalCase/eu/page.tsx new file mode 100644 index 000000000..9e5300b39 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/globalCase/eu/page.tsx @@ -0,0 +1,142 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/eu/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[EU] 법률을 제정, EU국가에서 물 재이용 유도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "EU", href: "/reuse/business/globalCase/eu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/eu_01.png", alt: "EU 사례 1" }, + ]} + /> + + {/* 물 재이용에 대한 법률을 제정(2020 제정, 2023 시행) */} + <section className="mt-8"> + <SectionTitle>물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)</SectionTitle> + <div className="px-4 mt-6"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741) + </li> + <li> + • 2023년부터 모든 EU국가에서 시행 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 유럽연합 회원국 물 재이용 지원정책 */} + <section className="mt-6"> + <SectionTitle>유럽연합 회원국 물 재이용 지원정책</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원<br /> + <ul className="list-inside space-y-4 text-[#767676] text-sm ml-4 mt-2"> + <li>– 2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용 물 재이용 프로그램<br/> + <ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2"> + <li>1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의 일환으로 80만 유로를 지원</li> + <li>2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가 실시</li> + </ul> + </li> + <li>– 물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원<br/> + <ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2"> + <li> *프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유</li> + </ul> + </li> + </ul> + </li> + <li> + • EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원<br /> + <ul className="list-inside space-y-2 text-[#767676] text-sm ml-4 mt-2"> + <li>‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management Community of Practice)’ 시범사업 수행</li> + <li>유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용 솔루션을 확장할 수 있는 기술을 제공</li> + </ul> + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로 예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족 문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대, 염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관 변화와 산업용수 수요 증가 등으로 악화되고 있는 상황</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을 비롯한 남부유럽 에서 특히 심각함. 전반적으로 북유럽에 비해 남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스 동부지역의 물 스트레스가 심각함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water Framework Directive을 개발, 이후 2020년 재이용에 관한 법률을 제정</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/overview/page.tsx b/apps/dm_new/app/(landing)/reuse/business/globalCase/overview/page.tsx new file mode 100644 index 000000000..b8ad773af --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/globalCase/overview/page.tsx @@ -0,0 +1,229 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/overview/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="해외 재이용 사례 개요" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "사례개요", href: "/reuse/business/globalCase/overview" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/overview_01.png", alt: "해외 재이용 사례 1" }, + ]} + /> + + <section className="mt-8"> + <div className="rounded-md bg-[#e9f0f1] p-8"> + <h3 className="text-xl font-bold text-gray-800 leading-relaxed"> + 2024년 하수도 통계 기준 15.6%이며,<br /> + 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 <span className="font-extrabold">17%</span> + </h3> + + {/* 진행 바 영역 */} + <div className="mt-6 space-y-4"> + {/* 2024 */} + <div className="flex items-center gap-4"> + <span className="text-sm text-gray-600">’24년도</span> + <div className="relative h-7 rounded-r-lg"> + <div className="h-7 w-[112px] rounded-r-lg bg-gray-400" /> + </div> + <span className="text-sm text-gray-700">15.6%</span> + </div> + + {/* 2030 */} + <div className="flex items-center gap-4"> + <span className="text-sm text-gray-600">’30년도</span> + <div className="relative"> + <div className="h-10 w-[327px] rounded-r-lg bg-gradient-to-r from-sky-500 to-green-500" /> + </div> + <span className="text-xl font-bold text-gray-900">17%</span> + </div> + </div> + </div> + + {/* 설명 문구 */} + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 우리나라의 하수 처리 재이용률은 <span className="text-sky-600 ">15% 수준</span> + </li> + <li> + • 대만은 가뭄과 공업용수 증가에 대응하여 + <span className="text-sky-600 "> + {" "}현재 4.3% 수준이며, 16.5%까지 확대 계획 수립 + </span> + </li> + </ul> + + {/* 테이블 */} + <div className="mt-6"> + <table className="w-full text-center text-sm"> + <thead> + <tr> + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">해외정보</th> + <th className="bg-teal-500 px-4 py-3 text-white rounded-t-lg">현재</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">미래(목표, 예측치)</th> + </tr> + </thead> + <tbody className="bg-white border border-t-0"> + <tr className="border-t"> + <td className="px-4 py-3 font-medium">한국</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">12.1%</td> + <td className="px-4 py-3">15.6%</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">17%</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 font-medium">대만</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">2.4%</td> + <td className="px-4 py-3">4.3%</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">16.5%</td> + </tr> + </tbody> + </table> + </div> + </section> + + <Hr className="my-11" /> + + {/* 주요 국가의 하수처리수 재이용률 (%) */} + <section className="pb-[200px]"> + <SectionTitle>주요 국가의 하수처리수 재이용률 (%)</SectionTitle> + {/* 설명 문구 */} + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐 + </li> + <li> + • 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편 + </li> + </ul> + {/* 테이블 (2단 병렬: 좌/우 데이터) */} + <div className="mt-6"> + <div className="overflow-x-auto"> + <table className="min-w-[900px] w-full text-center text-sm"> + <thead> + <tr> + {/* 좌측 3컬럼 */} + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th> + + {/* 우측 3컬럼 */} + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th> + </tr> + </thead> + + <tbody className="bg-white border border-t-0"> + {[ + { + L: { c: "이스라엘", r: "중동&북아프리카", v: "92.3" }, + R: { c: "튀르키예", r: "동유럽&중앙아시아", v: "26.6" }, + }, + { + L: { c: "쿠웨이트", r: "중동&북아프리카", v: "89.3" }, + R: { c: "독일", r: "서유럽", v: "25.0" }, + }, + { + L: { c: "호주", r: "동아시아&태평양", v: "49.0" }, + R: { c: "스페인", r: "서유럽", v: "24.4" }, + }, + { + L: { c: "싱가포르", r: "동아시아&태평양", v: "44.4" }, + R: { c: "한국", r: "동아시아&태평양", v: "12.1" }, + }, + { + L: { c: "그리스", r: "서유럽", v: "39.1" }, + R: { c: "미국", r: "북아메리카", v: "13.1" }, + }, + { + L: { c: "사우디아라비아", r: "중동&북아프리카", v: "32.8" }, + R: { c: "프랑스", r: "서유럽", v: "9.5" }, + }, + { + L: { c: "이탈리아", r: "서유럽", v: "31.7" }, + R: { c: "대만", r: "동아시아&태평양", v: "2.4" }, + }, + { + L: { c: "룩셈부르크", r: "서유럽", v: "29.1" }, + R: { c: "일본", r: "동아시아&태평양", v: "0.8" }, + }, + { + L: { c: "포르투갈", r: "서유럽", v: "29.0" }, + R: { c: "", r: "", v: "" }, // 마지막 우측은 빈칸(이미지처럼) + }, + ].map((row, idx) => ( + <tr key={idx} className="border-t"> + {/* 좌측 */} + <td className="px-4 py-3 font-medium text-gray-700">{row.L.c}</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">{row.L.r}</td> + <td className="px-4 py-3 bg-green-50 text-gray-700">{row.L.v}</td> + + {/* 우측 */} + <td className="px-4 py-3 font-medium text-gray-700">{row.R.c}</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">{row.R.r}</td> + <td className="px-4 py-3 bg-green-50 text-gray-700">{row.R.v}</td> + </tr> + ))} + </tbody> + </table> + </div> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/singapore/page.tsx b/apps/dm_new/app/(landing)/reuse/business/globalCase/singapore/page.tsx new file mode 100644 index 000000000..a876a3180 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/globalCase/singapore/page.tsx @@ -0,0 +1,172 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/singapore/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[싱가포르] 수요가 높은 산업의 물 재이용 의무화" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "싱가포르", href: "/reuse/business/globalCase/singapore" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/singapore_01.png", alt: "싱가포르 사례 1" }, + ]} + /> + + {/* 싱가포르 NEWater 비중 확대 */} + <section className="mt-8"> + <SectionTitle>싱가포르 NEWater 비중 확대</SectionTitle> + <div className="px-4 mt-6"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설 + </li> + <li> + • NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용 + </li> + <li> + • 2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 싱가포르 재이용수 의무화 및 지원정책 */} + <section className="mt-6"> + <SectionTitle>싱가포르 재이용수 의무화 및 지원정책</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 공업용수 수요가 높은 산업에 대해 물 재이용을 의무화<br /> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>– 반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 → 공업용수 수요 원인</li> + <li>– 2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는 재이용수 사용 의무화</li> + <li>– 반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는 43% 정도)</li> + </ul> + </li> + <li> + • 재정 지원을 통해 재이용 활성화를 지원<br /> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>– 재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의 2가지 금융상품 제공<br/> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>1. Water Efficiency Fund</li> + <li>2. Industrial Water Solutions Demonstration Fund</li> + </ul> + </li> + <li className="mt-2">– 2025년 기준 지원 한도 상향 → 물 재이용 활성 도모<br/> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>투입비용 50% → 75% 지원 / WSDP: $400만 → $500만</li> + <li>물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원 가능</li> + </ul> + </li> + </ul> + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년 맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을 계속하고 있음<br/> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>- 싱가포르는 국토 내에 호수와 하천이적고 집수공간도 적어 빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에 불과함</li> + </ul> + </li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 도심에 저수지를 건설하 고 건물에 빗물이용시설을 건설하여 빗물을 재이용하고 있음</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 2002년부터 하수재이용수인 NEWater를 개발, 비음 용수와 간접음용수로 활용하고 있음<br/> + <ul className="list-inside text-[#767676] text-sm/6 ml-4 mt-2"> + <li>- 재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한 신뢰 확보를 위해 NEWater 방문자 센터도 건설함</li> + <li>- 비음용수로 많이 사용하며 건기에는 NEWater를 저수지에 추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에 수돗물로 공급함</li> + <li>- NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장, 산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이 사용하는 곳은 웨이퍼 제조 공장</li> + </ul> + </li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며, Non-domestic 기준 NEWater 가격은 $2.41</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가 포함됨</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재 이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/taiwan/page.tsx b/apps/dm_new/app/(landing)/reuse/business/globalCase/taiwan/page.tsx new file mode 100644 index 000000000..b296ae298 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/globalCase/taiwan/page.tsx @@ -0,0 +1,216 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/taiwan/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[대만] 국가 주도의 공업용수 공급" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "대만", href: "/reuse/business/globalCase/taiwan" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/taiwan_01.png", alt: "대만 사례 1" }, + ]} + /> + + {/* 재생 수자원 개발법 제정(2015년제정, 2022개정) */} + <section className="mt-8"> + <SectionTitle>재생 수자원 개발법 제정(2015년제정, 2022개정)</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-2 text-base text-gray-600"> + <li> + • 목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결 + </li> + <li> + • 내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등 + </li> + </ul> + <div className="rounded-sm bg-[#e9f0f1] p-8 mt-5"> + <ul className="space-y-1 text-2xl text-[#1A1A1A]"> + <li> + – 제4조. 물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율 재이용수 사용 규정) + </li> + <li> + – 제5조. 재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수 무상공급 제공 가능) + </li> + <li> + – 제7조. 재이용수 이용 범위, 이용량 재이용수 수질 기준 내용 (식품, 제약산업 사용 불가) + </li> + <li> + – 제8조. 재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우, 정부가 지자체 지정) + </li> + </ul> + </div> + </div> + </section> + + <Hr className="my-11" /> + + {/* 대만정부 하수재이용 사업 시행 */} + <section className="mt-6"> + <SectionTitle>대만정부 하수재이용 사업 시행</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑)<br /> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li> + <li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li> + </ul> + </li> + <li> + • 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능 + </li> + <li> + • 공급단가는 수질과 운영조건에 따라 다르게 책정 + </li> + </ul> + {/* 테이블 (2단 병렬: 좌/우 데이터) */} + <div className="mt-3"> + <div className="overflow-x-auto"> + <p className="text-center text-2xl py-2">〈대만 재이용 시설별 재이용수 공급단지〉</p> + <table className="min-w-[900px] w-full text-center text-sm"> + <thead className="border"> + <tr> + <th className="bg-[#C2CAD2] px-4 py-3 text-black">사업</th> + <th className="bg-sky-600 px-4 py-3 text-white">가격(NTD/㎥)</th> + <th className="bg-green-500 px-4 py-3 text-white">가격(원/㎥)</th> + </tr> + </thead> + + <tbody className="bg-white text-[#4F4F4F] border border-t-0"> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Fongshan(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">23.39</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,038원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Linhai(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">19.28</td> + <td className="px-4 py-3 bg-[#E7F4E2]">858원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Anping(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">38.28</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,699원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Yongkang(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">23.67</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,050원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Rende(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">28.26</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,254원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Shuinan(타이중)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">15.00</td> + <td className="px-4 py-3 bg-[#E7F4E2]">668원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Quaitou(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">35.44</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,573원</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 풍부한 강수량에도, 지형적 특성으로 인해 수자원 확보에 어려움을 겪고 있으며, 대만 남부지역의 경우 우기와 건기 사이의 강수량 차이가 큼</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 최근 심각한 가뭄이 여러 해에 걸쳐서 발생하고 있으며, 강수량 감소와 함께 대만 정부 주도하에 반도체 산업을 국가 핵심 산업으로 선정하고 반도체 산업 육성 계획을 세움. 그 결과, 2023년 파운드리 시장의 68.3%를 차지하는 반도체 강국으로 성장했으며 이로 인한 공업용수 수요가 크게 증가</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>반도체 산업은 물 소비가 많아, 안정적인 용수공급이 매우 중요한데 현재 19개의 반도체 공장이 물이 부족한 대만 남부지역에 위치함. 2021년 가뭄 발생 당시, 산업단지 기업들도 용수 공급에 어려움을 겪음. 이는 대만 남부 지역의 물 부족, 물 분배 등에 대한 문제를 야기함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만 정부는 산업단지 기업들에게 2023년부터 물에 대한 추가요금 부과를 시작했으며, 농업용수에 대해서는 강력한 억제정책을 시행함. 안정적인 용수 공급을 위해서 정부와 기업에서 자체적으로 재이용수 시설에 대한 투자가 이루어지고 있음.</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 2004년 기후변화로 인한 물 부족 상황을 해결하기 위해 수자원 보호 전략을 승인하고, 2015년 대만 환경부는 재생 수자원 개발법을 발표, 2022년에는 한 차례 개정</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 물 소비량이 많은 산업(high water consuming industries)과 하수처리장(municipal wastewater treatment plants), 두 곳을 재이용수의 주요 공급원으로 계획</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/layout.tsx b/apps/dm_new/app/(landing)/reuse/business/layout.tsx new file mode 100644 index 000000000..dc62f77cc --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/layout.tsx @@ -0,0 +1,16 @@ +// apps/dm/app/(landing)/reuse/business/layout.tsx + +import type { ReactNode } from "react"; +import Lnb from "../_components/Lnb"; +import { LNB_BUSINESS } from "../_constants/lnb"; + +export default function BusinessLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb title="사례" items={LNB_BUSINESS} /> + <main className="flex-1 px-[100px] pt-[80px]"> + {children} + </main> + </div> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/business/rain/suwon/page.tsx b/apps/dm_new/app/(landing)/reuse/business/rain/suwon/page.tsx new file mode 100644 index 000000000..3acba860b --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/rain/suwon/page.tsx @@ -0,0 +1,93 @@ +// apps/dm/app/(landing)/reuse/business/rain/suwon/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="경기도 수원시 빗물이용과 중수도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "빗물 재이용 사례" }, + { label: "수원시", href: "/reuse/business/rain/suwon" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/rain/rain_01.png", alt: "수원시 하수 재이용 1" }, + { src: "/images/landing/business/rain/rain_02.png", alt: "수원시 하수 재이용 2" }, + { src: "/images/landing/business/rain/rain_03.png", alt: "수원시 하수 재이용 3" }, + { src: "/images/landing/business/rain/rain_04.png", alt: "수원시 하수 재이용 4" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 경기도 수원시 빗물이용과 중수도 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 방식 */} + <section className="pb-[200px]"> + <SectionTitle>사업 방식</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + (빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로 비산먼지 저감<br/> + ※ 시설 가동중 도로 노면 온도의 7.2 ~ 26.3℃ 감소 효과 확인(‘21.7월, 수원시청).<br/> + 다만, 가뭄기나 강우량이 적을 경우 빗물을 원활히 공급하기 다소 곤란<br/><br/> + - (중수도) 수원시청, 장안구청, 월드컵경기장에 빗물 연계 중수도를 화장실·조경용수 등으로 사용하여 수도요금 절감(연 7백~30백만원) + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/bolyeong/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/bolyeong/page.tsx new file mode 100644 index 000000000..04569f264 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/bolyeong/page.tsx @@ -0,0 +1,111 @@ +// apps/dm/app/(landing)/reuse/business/sewage/bolyeong/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="충남보령 하수 재이용" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "충청남도 보령시", href: "/reuse/business/sewage/bolyeong" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/bolyeong_01.png", alt: "보령시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 충남 보령 하수재이용<br/> + : 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 방식 */} + <section className="mt-6"> + <SectionTitle>사업 방식</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결 + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 처리공정 : 전처리 UF + 후처리 RO<br/> + 시설용량 : 1만m³/일<br/> + 관로 : 8.7km<br/> + 사업비 : 315억원<br/> + 위치: 충청남도 보령시 대청방조제로 322 일원 + </p> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로<br/> + 국가전력 기반시설의 안정적 운영 도모 + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/cheongju/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/cheongju/page.tsx new file mode 100644 index 000000000..1cddcae80 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/cheongju/page.tsx @@ -0,0 +1,94 @@ +// apps/dm/app/(landing)/reuse/business/sewage/cheongju/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="청주시 하수 재이용 시설" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "청주시", href: "/reuse/business/sewage/cheongju" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/cheongju_01.png", alt: "청주시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 청주시 하수 재이용 시설 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="pb-[200px]"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 청주시 하수처리수 재이용 민간투자사업(BTO)<br/> + 공급량 : 35,000㎥/일<br/> + 공급관로 : 8.3㎞<br/> + 사업비 : 622억원<br/> + 수요처 : SK하이닉스 등 일반산업단지<br/> + 지역 (위치) : 충청북도 청주시 흥덕구 가락리 135-9번지 일원<br/> + 처리용량 : 35.000(m³/일)<br/> + 적용기술 : 분리막 (UF : 전처리), (RO : 주처리) + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/guri/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/guri/page.tsx new file mode 100644 index 000000000..638a8c8b6 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/guri/page.tsx @@ -0,0 +1,148 @@ +// apps/dm/app/(landing)/reuse/business/sewage/guri/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="구리시 도로자동청소시스템" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "구리시", href: "/reuse/business/sewage/guri" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/guri_01.png", alt: "구리시 도로자동청소시스템 1" }, + { src: "/images/landing/business/sewage/guri_02.png", alt: "구리시 도로자동청소시스템 2" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 구리시 도로자동청소시스템 설치사업 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업목적 */} + <section className="mt-6"> + <SectionTitle>사업목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : 61억원<br/> + 위 치 : 구리시 경춘로 및 갈매중앙로 일대 등<br/> + 관 로 : 5.8km<br/> + 공급계획<br/> + </p> + </section> + + <Hr className="my-11" /> + + {/* 공급계획 */} + <section className="pb-[200px]"> + <SectionTitle>공급계획</SectionTitle> + {/* 데이터 테이블 */} + <div className="overflow-x-auto mt-8"> + <table className="w-full border border-[#E3E7EA] text-center"> + <thead> + <tr> + <th className="bg-[#01ACE5] text-white font-semibold py-4 border border-gray-300"> + 시설명 + </th> + <th className="bg-[#33B5C8] text-white font-semibold py-4 border border-gray-300"> + 재이용수 용도 + </th> + <th className="bg-[#55BCA2] text-white font-semibold py-4 border border-gray-300"> + 공급량(㎥/일) + </th> + <th className="bg-[#6DBB4F] text-white font-semibold py-4 border border-gray-300"> + 원수 + </th> + </tr> + </thead> + <tbody> + <tr> + <td + className="bg-white py-6 border border-gray-300 text-gray-700" + > + 도로자동청소시스템 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 세척살수용수 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 178 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 구리 공공하수처리장 재이용수 + </td> + </tr> + </tbody> + </table> + </div> + </section> + + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/hwanggu/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/hwanggu/page.tsx new file mode 100644 index 000000000..ef0baa15d --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/hwanggu/page.tsx @@ -0,0 +1,173 @@ +// apps/dm/app/(landing)/reuse/business/sewage/hwanggu/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="수원시 황구지천 하천유지용수" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "수원시 황구지천", href: "/reuse/business/sewage/hwanggu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/hwanggu_01.png", alt: "수원시 황구지천 하수 재이용 1" }, + { src: "/images/landing/business/sewage/hwanggu_02.png", alt: "수원시 황구지천 하수 재이용 2" }, + { src: "/images/landing/business/sewage/hwanggu_03.png", alt: "수원시 황구지천 하수 재이용 3" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 수원시 황수원시 황구지천 하수처리수 재이용사업 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업목적 */} + <section className="mt-6"> + <SectionTitle>사업목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : 110억원<br/> + 위 치 : 수원시 권선구 호매실천, 금곡천<br/> + 관 로 : 3.9km<br/> + 공 급 량 : 25,000㎥/일(하수처리시설 용량: 45,000㎥/일)<br/> + </p> + </section> + + <Hr className="my-11" /> + + {/* 공급계획 */} + <section className="pb-[200px]"> + <SectionTitle>공급계획</SectionTitle> + {/* 데이터 테이블 */} + <div className="overflow-x-auto mt-8"> + <table className="w-full border border-[#E3E7EA] text-center"> + <thead> + <tr> + {/* 구분 */} + <th className="w-1/4 bg-[#E3E7EA] text-black font-semibold py-4 border border-gray-300"> + 구분 + </th> + + {/* 공급량 */} + <th className="w-2/4 bg-[#1FA3C6] text-white font-semibold py-4 border border-gray-300"> + 공급량 + </th> + + {/* 비고 */} + <th className="w-2/4 bg-[#72B84F] text-white font-semibold py-4 border border-gray-300"> + 비고 + </th> + </tr> + </thead> + + <tbody> + <tr> + {/* 좌측 병합 */} + <td + rowSpan={3} + className="bg-white font-medium text-gray-700 border border-gray-300" + > + 하천 등 유지용수 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 12,500m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 호매실천 공급 + </td> + </tr> + + <tr> + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 6,250m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 금곡천1 공급 + </td> + </tr> + + <tr> + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 6,250m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 금곡천2 공급 + </td> + </tr> + </tbody> + </table> + </div> + </section> + + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/paju/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/paju/page.tsx new file mode 100644 index 000000000..7c76f0fa9 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/paju/page.tsx @@ -0,0 +1,112 @@ +// apps/dm/app/(landing)/reuse/business/sewage/paju/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="경기도 파주시 하수 재이용" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "파주시", href: "/reuse/business/sewage/paju" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/paju_01.png", alt: "파주시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 경기도 파주시 하수재이용<br/> + : 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급<br/> + *LG디스플레이(P10공장), 희성전자, 엔비텍 등 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 배경 및 목적 */} + <section className="mt-6"> + <SectionTitle>사업 배경 및 목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스) + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 처리공정 : 전처리 UF + 후처리 RO<br/> + 공급량 : 4만m³/일<br/> + 관로 : 13.6km<br/> + 사업비 : 651억원<br/> + 위치 : 경기도 파주시 금촌동 714번지 일원<br/> + 시설용량 : 41,20m³/일 + </p> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례 + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/yeosu/page.tsx b/apps/dm_new/app/(landing)/reuse/business/sewage/yeosu/page.tsx new file mode 100644 index 000000000..e86d61b92 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/business/sewage/yeosu/page.tsx @@ -0,0 +1,101 @@ +// apps/dm/app/(landing)/reuse/business/sewage/yeosu/page.tsx +"use client"; + +import SubPageHeader from "../../../_components/SubPageHeader"; +import CaseImageSlider from "../../../_components/ImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="여수시 하수처리수 재이용 민간투자사업(BTO)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "여수시", href: "/reuse/business/sewage/yeosu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/yeosu_01.png", alt: "여수시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 여수시 하수처리수 재이용 민간 투자사업(BTO) + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 목적 */} + <section className="mt-6"> + <SectionTitle>사업 목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 수요자가 원하는 양질의 공업용수를 저렴하게 곰급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선 + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="pb-[200px]"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : GS칼텍스, YNCC(주) 등 750억원<br/> + 시행사 : 여수그린허브(주)<br/> + 운영사 : 테크로스<br/> + 위치: 여수시 신월로 2844일원 (여수 하수처리시설 부지 내)<br/> + 처리공법 : 전처리시설(MF) + 주처리시설(RO)<br/> + 시설용량 및 공급량 : 5만m³/일<br/> + 관로 : 18.6km + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx new file mode 100644 index 000000000..6d4d00af1 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx @@ -0,0 +1,102 @@ +// apps/dm/app/(landing)/reuse/cases/greywater/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import greywaterOverall from "@/public/images/landing/cases/greywater_overall.png"; +import greywaterFactory from "@/public/images/landing/cases/greywater_factory.png"; +import greywaterBuilding from "@/public/images/landing/cases/greywater_building.png"; +import greywaterUsage from "@/public/images/landing/cases/greywater_usage.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="중수도(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "중수도(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외) */} + <section className="mt-6"> + <SectionTitle>전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterOverall} alt="전체 중수도 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 중수공장 및 발전시설 내 중수도 설치 및 이용 현황 */} + <section> + <SectionTitle>공장 및 발전시설 내 중수도 설치 및 이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterFactory} alt="공장 및 발전시설 내 중수도 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 중수도 건축물별 이용량 현황 */} + <section className="pb-16"> + <SectionTitle>중수도 건축물별 이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterBuilding} alt="중수도 건축물별 이용량 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 중수도 용도별 이용량 현황 */} + <section className="pb-[200px]"> + <SectionTitle>중수도 용도별 이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterUsage} alt="중수도 용도별 이용량 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/cases/layout.tsx b/apps/dm_new/app/(landing)/reuse/cases/layout.tsx new file mode 100644 index 000000000..5783c4b58 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/cases/layout.tsx @@ -0,0 +1,16 @@ +// apps/dm/app/(landing)/reuse/cases/layout.tsx + +import type { ReactNode } from "react"; +import Lnb from "../_components/Lnb"; +import { LNB_CASES } from "../_constants/lnb"; + +export default function CasesLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb title="현황" items={LNB_CASES} /> + <main className="flex-1 px-[100px] pt-[80px]"> + {children} + </main> + </div> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx new file mode 100644 index 000000000..3d7aa9a3f --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx @@ -0,0 +1,91 @@ +// apps/dm/app/(landing)/reuse/cases/rain/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import rainwaterBuilding from "@/public/images/landing/cases/rainwater_building.png"; +import rainwaterUsage from "@/public/images/landing/cases/rainwater_usage.png"; +import rainwaterUsageAmount from "@/public/images/landing/cases/rainwater_usage_amount.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="빗물이용시설(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "빗물이용시설(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterBuilding} alt="빗물이용시설의 건축물별 빗물이용량 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */} + <section> + <SectionTitle>빗물이용시설의 용도별 분포 (2023년 기준)</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterUsage} alt="빗물이용시설의 용도별 분포 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */} + <section className="pb-[200px]"> + <SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterUsageAmount} alt="빗물이용시설의 용도별 빗물이용량 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx new file mode 100644 index 000000000..b9e9073c0 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx @@ -0,0 +1,102 @@ +// apps/dm/app/(landing)/reuse/cases/treated/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import treatedWaterYear from "@/public/images/landing/cases/treated_water_year.png"; +import treatedWaterUsage from "@/public/images/landing/cases/treated_water_usage.png"; +import treatedWaterFacility from "@/public/images/landing/cases/treated_water_facility.png"; +import treatedWaterRegion from "@/public/images/landing/cases/treated_water_region.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="하수처리수 재이용시설(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "하수처리수 재이용시설(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 연도별 하수처리수 재이용 현황 */} + <section className="mt-6"> + <SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterYear} alt="연도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 용도별 하수처리수 재이용 현황 */} + <section> + <SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterUsage} alt="용도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 하수처리시설 설치 및 이용 현황 */} + <section> + <SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterFacility} alt="하수처리시설 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 시도별 하수처리수 재이용 현황 */} + <section className="pb-[200px]"> + <SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterRegion} alt="시도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx new file mode 100644 index 000000000..d694d040e --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx @@ -0,0 +1,91 @@ +// apps/dm/app/(landing)/reuse/intro/greywater/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png"; +import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png"; +import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="중수도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "중수도" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 중수 재이용 소개 */} + <section className="mt-6"> + <SectionTitle>중수 재이용 소개</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterIntro} alt="중수 재이용 소개" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 중수도 설치 의무 대상 */} + <section> + <SectionTitle>중수도 설치 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 중수도 시설기준 및 관리기준 */} + <section className="pb-[200px]"> + <SectionTitle>중수도 시설기준 및 관리기준</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx new file mode 100644 index 000000000..c9952bf0b --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx @@ -0,0 +1,130 @@ +// apps/dm/app/(landing)/reuse/intro/law/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import lawTimeline from "@/public/images/landing/intro/law_timeline.png"; +import performanceLeft from "@/public/images/landing/intro/performance_left.png"; +import domesticTextbox from "@/public/images/landing/intro/domestic_textbox.png"; +import policyTimeline from "@/public/images/landing/intro/policy_timeline.png"; +import relNote from "@/public/images/landing/intro/rel_note.png"; +import linkage from "@/public/images/landing/intro/linkage.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function WaterReuseLawPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="물 재이용 법" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "물 재이용 법" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 법제도/계획 수립 타임라인 */} + <section className="mt-6"> + <SectionTitle>법제도 · 계획 및 기본계획 수립</SectionTitle> + <div className="mt-8"> + <ImgCard src={lawTimeline} alt="법제도 타임라인" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 제2차 계획 대비 실적 현황 (2컬럼) */} + <section> + <SectionTitle>제 2차 계획 대비 실적 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={performanceLeft} alt="실적 현황 요약" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 국내 관련 현황 */} + <section> + <SectionTitle>국내 관련 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={domesticTextbox} alt="국내 관련 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 정책 변천 추이 */} + <section> + <SectionTitle>물 재이용 정책 변천 추이</SectionTitle> + <div className="mt-8"> + <ImgCard src={policyTimeline} alt="정책 변천 타임라인" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 5) 타 법령과의 관계도 */} + <section> + <SectionTitle>타법령과의 관계도</SectionTitle> + <div className="mt-8"> + <ImgCard src={relNote} alt="관계도 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 6) 국내 연계 현황 */} + <section className="pb-[200px]"> + <SectionTitle>국내 연계 현황</SectionTitle> + <div className="mt-8"> + <div className="mx-[20px] mb-8"> + <p className="text-[24px] font-semibold mb-2">주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요</p> + <p className="text-[#4F4F4F]">국가 상위계획의 정책 방향이 기후위기 대응 등 수자원의 공급 안전성을 위한 물 재이용 활성화로 정책 실효성 제고 필요<br /> + • 물관리 계획(국가수도기본계획, 국가 및 유역하수도종합계획, 하천유역수자원관리계획 등)과의 연계성 확보 → 통합적인 관점에서 물이용 체계 구축 필요 + </p> + </div> + <ImgCard src={linkage} alt="국내 연계 현황" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/intro/layout.tsx b/apps/dm_new/app/(landing)/reuse/intro/layout.tsx new file mode 100644 index 000000000..5b5232546 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/intro/layout.tsx @@ -0,0 +1,16 @@ +// apps/dm/app/(landing)/reuse/intro/layout.tsx + +import type { ReactNode } from "react"; +import Lnb from "../_components/Lnb"; +import { LNB_INTRO } from "../_constants/lnb"; + +export default function IntroLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb title="소개" items={LNB_INTRO} /> + <main className="flex-1 px-[100px] pt-[80px]"> + {children} + </main> + </div> + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx new file mode 100644 index 000000000..fdd48aa5a --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx @@ -0,0 +1,103 @@ +// apps/dm/app/(landing)/reuse/intro/law/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import rainreuseIntro from "@/public/images/landing/intro/rainreuse_intro.png"; +import rainreuseMandatory from "@/public/images/landing/intro/rainreuse_mandatory.png"; +import rainwaterDesign from "@/public/images/landing/intro/rainwater_design.png"; +import rainwaterStandards from "@/public/images/landing/intro/rainwater_standards.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function RainwaterReusePage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="빗물 이용시설" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "빗물 이용시설" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 빗물 재이용 소개 */} + <section className="mt-6"> + <SectionTitle>빗물 재이용 소개</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainreuseIntro} alt="빗물 재이용 소개" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 빗물이용시설 설치 관리 법적 의무 대상 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설 설치 관리 법적 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainreuseMandatory} alt="법적 의무 대상 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 빗물이용시설 설계 방안 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설 설계 방안</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterDesign} alt="빗물이용시설 설계 방안 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 빗물이용시설 시설기준 및 관리기준 */} + <section className="pb-[200px]"> + <SectionTitle>빗물이용시설 시설기준 및 관리기준</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterStandards} alt="빗물이용시설 시설기준 및 관리기준 설명" /> + </div> + </section> + + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx new file mode 100644 index 000000000..e64299f70 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx @@ -0,0 +1,80 @@ +// apps/dm/app/(landing)/reuse/intro/treated/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "../../_components/SubPageHeader"; +import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png"; +import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function TreatedWaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="하수처리수" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "하수처리수" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 하수처리수 */} + <section className="mt-6"> + <SectionTitle>하수처리수</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedwaterIntro} alt="하수처리수 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 하수처리수 재이용시설 의무 대상 */} + <section className="pb-[200px]"> + <SectionTitle>하수처리수 재이용시설 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedwaterMandatory} alt="하수처리수 재이용시설 의무 대상 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/layout.tsx b/apps/dm_new/app/(landing)/reuse/layout.tsx new file mode 100644 index 000000000..7574782c9 --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/layout.tsx @@ -0,0 +1,19 @@ +// apps/dm/app/(landing)/reuse/layout.tsx +"use client"; + +import "./reuse.module.css"; + +import TopNav from "./_components/TopNav"; +import { usePathname } from "next/navigation"; + +export default function ReuseLayout({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const hasLnb = pathname !== "/reuse"; // 예: 메인만 제외 + + return ( + <div className="pt-[83px]"> + {hasLnb && <TopNav />} + <main className={hasLnb ? "ml-[260px]" : ""}>{children}</main> + </div> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/page.tsx b/apps/dm_new/app/(landing)/reuse/page.tsx new file mode 100644 index 000000000..ac6dcc9ac --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/page.tsx @@ -0,0 +1,60 @@ +// apps/dm/app/(landing)/reuse/page.tsx +"use client"; + +import { useMemo } from "react"; +import TopNav from "./_components/TopNav"; +import SideIndicator from "./_components/SideIndicator"; +import { useScrollSpy } from "./_hooks/useScrollSpy"; + +import Hero from "./_sections/Hero"; +import IntroCards from "./_sections/IntroCards"; +import Cases from "./_sections/Cases"; +import ProcessFlow from "./_sections/ProcessFlow"; + +const LANDING_SECTIONS = [ + { id: "main", label: "Main" }, + { + id: "intro", + label: "물 재이용 소개", // ✅ 상단 메뉴용 + sideLabel: "소개", // ✅ 사이드 인디게이터용 + }, + { + id: "cases", + label: "물 재이용 현황", // ✅ 상단 메뉴용 + sideLabel: "현황", // ✅ 사이드 인디게이터용 + }, + { + id: "processflow", + label: "물 재이용 사례", // ✅ 상단 메뉴용 + sideLabel: "재이용사례", // ✅ 사이드 인디게이터용 + }, +]; + +export default function ReuseLandingPage() { + const sectionIds = useMemo(() => LANDING_SECTIONS.map((s) => s.id), []); + const activeId = useScrollSpy(sectionIds); + + return ( + <main className="bg-white"> + + {/* 헤더 */} + <TopNav /> + + {/* 사이드 인디게이터 */} + <SideIndicator items={LANDING_SECTIONS} activeId={activeId} /> + + {/* 컨텐츠 영역 */} + <div className="[&>section]:scroll-mt-[83px]"> + {/* 물 재이용 */} + <Hero /> + {/* 재이용 소개 */} + <IntroCards /> + {/* 재이용 사례 */} + <Cases /> + {/* 물 재이용 사업 */} + <ProcessFlow /> + </div> + + </main> + ); +} diff --git a/apps/dm_new/app/(landing)/reuse/reuse.module.css b/apps/dm_new/app/(landing)/reuse/reuse.module.css new file mode 100644 index 000000000..31631d7ba --- /dev/null +++ b/apps/dm_new/app/(landing)/reuse/reuse.module.css @@ -0,0 +1,238 @@ +/* apps/dm/app/(landing)/reuse/reuse.module.css */ + +.hero { + position: relative; + min-height: 100vh; + overflow: hidden; + font-family: "NotoSansKR", sans-serif; +} + +.heroBg { + position: absolute; + inset: 0; + background: url("/images/landing/hero_bg.jpg") center/cover no-repeat; + transform: scale(1.02); +} + +.heroOverlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.50); +} + +/* ===== Side Indicator (첨부 이미지 스타일) ===== */ +.sideIndicator { + position: fixed; + right: 28px; + top: 50%; + transform: translateY(-50%); + z-index: 40; + + /* 원형 배지 폭만큼만 */ + width: 60px; + + /* (디자인처럼) 스크롤 영역 위에 뜨되 클릭 가능 */ + pointer-events: none; +} + +/* 세로 라인: 연한 하늘색 + 점선(도트) 느낌 */ +.sideIndicatorLine { + position: absolute; + left: 50%; + top: -150px; + bottom: -150px; + width: 2px; + transform: translateX(-50%); + background: rgba(46, 177, 235, 0.25); + background-size: 2px 16px; + background-repeat: repeat-y; + z-index: -1; +} + +/* 리스트는 가운데 정렬로 수직 배치 */ +.sideIndicatorList { + list-style: none; + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + align-items: center; + + gap: 56px; /* 4개 간격(시안에 맞게 조절) */ + pointer-events: auto; +} + +.sideIndicatorItem { + display: flex; + justify-content: center; +} + +/* 공통 버튼 */ +.indicatorBtn { + border: 0; + background: transparent; + padding: 0; + cursor: pointer; + position: relative; + z-index: 2; + display: grid; + place-items: center; +} + +/* ✅ 기본: 작은 점 */ +.indicatorDot { + width: 8px; + height: 8px; + border-radius: 999px; + background: rgba(30, 167, 225, 0.55); + border: 1px solid rgba(30, 167, 225, 0.7); +} + +/* ✅ 활성: 커지는 큰 원(텍스트 포함) */ +.indicatorActive { + width: 83px; + height: 83px; + border-radius: 999px; + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +/* Main 활성: 흰 배경 + 검정 텍스트 */ +.indicatorMainActive { + background: #ffffff; + border: 2px solid rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.75); +} + +/* 섹션 활성: 파란 배경 + 흰 텍스트 */ +.indicatorSectionActive { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #ffffff; + /* box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); */ +} + +/* 활성 텍스트 */ +.indicatorText { + font-size: 12px; + font-weight: 700; + line-height: 1.1; + text-align: center; + padding: 0 8px; + word-break: keep-all; + white-space: normal; +} + +/* 공통 원형 버튼 */ +.sideIndicatorDot { + width: 58px; + height: 58px; + border-radius: 999px; + + display: grid; + place-items: center; + + cursor: pointer; + user-select: none; + + transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, + border-color 0.15s ease; + + /* 라인 위로 올라오게 */ + position: relative; + z-index: 1; + + pointer-events: auto; +} + +.sideIndicatorDotText { + font-size: 12px; + font-weight: 600; + line-height: 1.1; + text-align: center; + padding: 0 8px; + + /* 긴 텍스트도 배지 안에서 줄바꿈 가능 */ + word-break: keep-all; + white-space: normal; +} + +/* Main: 흰색 원형 + 회색 텍스트 */ +.sideIndicatorDotMain { + background: #ffffff; + border: 2px solid rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.65); +} + +/* 섹션: 파란 원형 + 흰색 텍스트 */ +.sideIndicatorDotSection { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #ffffff; +} + +/* 활성 섹션: 더 진한 파랑 + 살짝 글로우 */ +.sideIndicatorDotActive { + transform: scale(1.02); + box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); +} + +/* hover 효과(이미지 느낌으로 아주 약하게만) */ +.sideIndicatorDot:hover { + transform: translateY(-1px); +} + +/* 반응형: 모바일에서는 숨김(원하시면 제거) */ +@media (max-width: 768px) { + .sideIndicator { + display: none; + } +} + +/* 중앙 배지 */ +.sideIndicatorBadge { + width: 58px; + height: 58px; + border-radius: 999px; + display: grid; + place-items: center; + font-size: 12px; + font-weight: 600; + text-align: center; + z-index: 1; +} + +/* Main 배지 */ +.badgeMain { + background: #fff; + border: 2px solid rgba(0,0,0,.15); + color: rgba(0,0,0,.65); +} + +/* Section 배지 */ +.badgeSection { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #fff; + box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); +} + +/* 작은 점 */ +.sideIndicatorDotSmall { + width: 7px; + height: 7px; + border-radius: 999px; + background: rgba(30,167,225,.6); +} + +/* 점 그룹 */ +.sideIndicatorDotsTop, +.sideIndicatorDotsBottom { + display: flex; + flex-direction: column; + align-items: center; + gap: 18px; +} + +.sideIndicatorDotsTop { margin-bottom: 18px; } +.sideIndicatorDotsBottom { margin-top: 22px; } \ No newline at end of file diff --git a/apps/dm_new/app/(with-layout)/apply/dmdco/page.tsx b/apps/dm_new/app/(with-layout)/apply/dmdco/page.tsx new file mode 100644 index 000000000..9422cd1ce --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/dmdco/page.tsx @@ -0,0 +1,577 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react"; +import SubPageHeader from "@/components/SubPageHeader"; +import PostcodeModal from "@/components/PostcodeModal"; +import type { AddrItem } from "@/app/store/sdmMng/postcode-service"; +import { useDmStore } from "@/app/store"; +import { fetchType, getFetch, postFetch, putFetch } from "@/components/ui/api"; +import { getCookie } from "@common/components/lib"; + +type Option = { value: string; label: string }; + +type SessionUser = { + dmdInfoId?: string; + dmdCoNm?: string; + zip?: string; + daddr?: string; + telno?: string; + ctpvCd?: string; + sggCd?: string; + rgtr?: string; + [key: string]: unknown; +}; + +const TOKEN_ID_COOKIE = "token-id"; +const FALLBACK_UUID = "c475824c-0e82-4a06-a282-2b55a20df265"; +const normalizeRegionName = (value: string) => value.replace(/\s+/g, "").trim(); +const isSameRegionName = (left: string, right: string) => { + const a = normalizeRegionName(left); + const b = normalizeRegionName(right); + return Boolean(a && b && (a === b || a.includes(b) || b.includes(a))); +}; +const formatKoreanPhoneNumber = (value: string) => { + const digits = value.replace(/\D/g, "").slice(0, 11); // 숫자만 추출하고 최대 11자리까지만 사용 + if (!digits) return ""; // 입력이 비어 있으면 빈 문자열 반환 + + if (/^1\d{0,7}$/.test(digits)) { // 15xx/16xx/18xx 같은 대표번호 패턴 + if (digits.length <= 4) return digits; // 아직 뒷자리가 부족하면 원문 유지 + return `${digits.slice(0, 4)}-${digits.slice(4, 8)}`; // xxxx-xxxx + } + + if (digits.startsWith("02")) { // 서울 지역번호(02) + const rest = digits.slice(2); // 국번/가입자번호 구간 + if (!rest) return "02"; // 02만 입력된 상태 + if (rest.length <= 3) return `02-${rest}`; // 02-xxx + if (rest.length <= 7) return `02-${rest.slice(0, 3)}-${rest.slice(3)}`; // 02-xxx-xxxx + return `02-${rest.slice(0, 4)}-${rest.slice(4, 8)}`; // 02-xxxx-xxxx + } + + if (digits.startsWith("0")) { // 02 제외 일반 지역번호/휴대폰(010 포함) + const area = digits.slice(0, 3); // 앞 3자리 식별자 + const rest = digits.slice(3); // 나머지 번호 + if (!rest) return area; // 식별자만 입력된 상태 + if (rest.length <= 3) return `${area}-${rest}`; // 0xx-xxx + if (rest.length <= 7) return `${area}-${rest.slice(0, 3)}-${rest.slice(3)}`; // 0xx-xxx-xxxx + return `${area}-${rest.slice(0, 4)}-${rest.slice(4, 8)}`; // 0xx-xxxx-xxxx + } + + if (digits.length <= 4) return digits; // 예외 입력: 앞 4자리까지 + if (digits.length <= 8) return `${digits.slice(0, 4)}-${digits.slice(4)}`; // xxxx-xxxx + return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 11)}`; // xxxx-xxxx-xxx +}; + +function Divider() { + return <div className="h-px w-full bg-neutral-200" />; +} + +function FormRow({ + label, + required, + children, +}: { + label: ReactNode; + required?: boolean; + children: ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6"> + <div className="flex items-center gap-1 text-lg font-semibold text-neutral-800"> + <span>{label}</span> + {required ? <span className="text-red-500">*</span> : null} + </div> + </div> + + <div className="col-span-12 lg:col-span-10 px-6 py-6">{children}</div> + </div> + ); +} + +function TextInput({ + value, + onChange, + placeholder, + type = "text", + disabled, + inputRef, + className = "", +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + type?: string; + disabled?: boolean; + inputRef?: RefObject<HTMLInputElement | null>; + className?: string; +}) { + return ( + <input + ref={inputRef} + type={type} + value={value} + disabled={disabled} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + className={[ + "h-11 w-full rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800", + "placeholder:text-neutral-400 focus:border-neutral-400 focus:outline-none", + disabled ? "bg-neutral-100 text-neutral-400" : "", + className, + ].join(" ")} + /> + ); +} + +function SelectBox({ + value, + onChange, + options, + className = "", +}: { + value: string; + onChange: (v: string) => void; + options: Option[]; + className?: string; +}) { + return ( + <select + value={value} + onChange={(e) => onChange(e.target.value)} + className={[ + "h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800", + "focus:border-neutral-400 focus:outline-none", + className, + ].join(" ")} + > + {options.map((o) => ( + <option key={o.value} value={o.value}> + {o.label} + </option> + ))} + </select> + ); +} + +export default function DemandPlaceRegisterPage() { + const { Common, initializeCommonData } = useDmStore((state) => state); + const { selectSidoData, selectSigunData } = Common; + + const [editMode, setEditMode] = useState(false); + const [sessionUser, setSessionUser] = useState<SessionUser | null>(null); + + const [sido, setSido] = useState(""); + const [sigungu, setSigungu] = useState(""); + + const [company, setCompany] = useState(""); + const [bizNo, setBizNo] = useState(""); + const [workerNo, setWorkerNo] = useState(""); + const [noWorkerNo, setNoWorkerNo] = useState(false); + + const [zip, setZip] = useState(""); + const [addr1, setAddr1] = useState(""); + const [addr2, setAddr2] = useState(""); + const [isPostcodeModalOpen, setIsPostcodeModalOpen] = useState(false); + const [focusDetailAddrOnClose, setFocusDetailAddrOnClose] = useState(false); + const detailAddrRef = useRef<HTMLInputElement | null>(null); + + const [tel, setTel] = useState(""); + const [pw, setPw] = useState(""); + const [pw2, setPw2] = useState(""); + + useEffect(() => { + initializeCommonData(); + }, [initializeCommonData]); + + useEffect(() => { + if (typeof window === "undefined") return; + + const rawUser = sessionStorage.getItem("user"); + if (!rawUser) { + setEditMode(false); + setSessionUser(null); + return; + } + + try { + const user = JSON.parse(rawUser) as SessionUser; + setSessionUser(user); + setEditMode(true); + + const fullId = String(user.dmdInfoId ?? "").trim(); + const bizDigits = fullId.slice(0, 10); + const workerDigits = fullId.slice(10, 14); + + setBizNo(bizDigits); + if (workerDigits && workerDigits !== "0000") { + setNoWorkerNo(false); + setWorkerNo(workerDigits); + } else { + setNoWorkerNo(true); + setWorkerNo(""); + } + + setCompany(String(user.dmdCoNm ?? "")); + setZip(String(user.zip ?? "")); + setAddr1(String(user.daddr ?? "")); + setAddr2(""); + setTel(formatKoreanPhoneNumber(String(user.telno ?? ""))); + setSido(String(user.ctpvCd ?? "")); + setSigungu(String(user.sggCd ?? "")); + } catch { + setEditMode(false); + setSessionUser(null); + } + }, []); + + useEffect(() => { + if (!focusDetailAddrOnClose || isPostcodeModalOpen) return; + + if (!addr1.trim()) { + setFocusDetailAddrOnClose(false); + return; + } + + const timer = setTimeout(() => { + detailAddrRef.current?.focus(); + setFocusDetailAddrOnClose(false); + }, 0); + + return () => clearTimeout(timer); + }, [addr1, focusDetailAddrOnClose, isPostcodeModalOpen]); + + const sidoOptions = useMemo<Option[]>(() => { + const mapped = selectSidoData + .filter((item) => item.val !== "0") + .map((item) => ({ value: item.val, label: item.text })); + + return [{ value: "", label: "선택" }, ...mapped]; + }, [selectSidoData]); + + const sigunguOptions = useMemo<Option[]>(() => { + if (!sido) return [{ value: "", label: "선택" }]; + + const mapped = selectSigunData + .filter((item) => item.val.substring(0, 2) === sido) + .map((item) => ({ value: item.val, label: item.text })); + + return [{ value: "", label: "선택" }, ...mapped]; + }, [sido, selectSigunData]); + + const formatBusinessNumber = (value: string) => { + const digitsOnly = value.replace(/\D/g, "").slice(0, 10); + if (digitsOnly.length <= 3) return digitsOnly; + if (digitsOnly.length <= 5) { + return `${digitsOnly.slice(0, 3)}-${digitsOnly.slice(3)}`; + } + return `${digitsOnly.slice(0, 3)}-${digitsOnly.slice(3, 5)}-${digitsOnly.slice(5)}`; + }; + + const checkDuplicate = async (fullDmdInfoId: string) => { + try { + const res = await getFetch(fetchType.WR, `/reuseFc/request/${fullDmdInfoId}`); + const responseData = (res?.data ?? res) as { dmdInfoId?: string } | undefined; + return Boolean(responseData?.dmdInfoId); + } catch { + return false; + } + }; + + const onSubmit = async () => { + const bizDigits = bizNo.replace(/\D/g, "").slice(0, 10); + const workerDigits = workerNo.replace(/\D/g, "").slice(0, 4); + const telDigits = tel.replace(/\D/g, ""); + const password = pw.trim(); + const passwordConfirm = pw2.trim(); + + if (!sido || sido === "0") return alert("시도/시군구를 선택해 주세요."); + if (!sigungu || sigungu === "0") return alert("시도/시군구를 선택해 주세요."); + if (!company.trim()) return alert("회사명을 입력해 주세요."); + if (company.trim().length > 20) return alert("회사명은 20자 이내로 입력해주세요."); + + if (!bizDigits) return alert("사업자등록번호를 입력해 주세요."); + if (!/^\d{10}$/.test(bizDigits)) { + return alert("사업자등록번호는 10자리 숫자만 입력 가능합니다."); + } + + if (!noWorkerNo) { + if (!workerDigits) return alert("종사업자등록번호를 입력해 주세요."); + if (!/^\d{4}$/.test(workerDigits)) { + return alert("종사업자등록번호는 4자리 숫자만 입력 가능합니다."); + } + } + + const mergedAddress = [addr1.trim(), addr2.trim()].filter(Boolean).join(" "); + if (!mergedAddress) return alert("사업자주소를 입력해 주세요."); + if (mergedAddress.length > 40) return alert("주소는 40자 이내로 입력해주세요."); + + if (!telDigits) return alert("전화번호를 입력해 주세요."); + if (!/^\d{8,11}$/.test(telDigits)) { + return alert("전화번호는 8~11자리 숫자만 입력 가능합니다."); + } + + if (!editMode) { + if (!password) return alert("비밀번호를 입력해 주세요."); + if (password.length < 8 || password.length > 16) { + return alert("비밀번호는 8자 이상 16자 이내로 입력해주세요."); + } + if (!passwordConfirm) return alert("비밀번호 확인을 입력해 주세요."); + if (password !== passwordConfirm) return alert("비밀번호가 일치하지 않습니다."); + } else if ((password || passwordConfirm) && password !== passwordConfirm) { + return alert("비밀번호가 일치하지 않습니다."); + } + + const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits); + + if (!editMode) { + const isDuplicate = await checkDuplicate(fullDmdInfoId); + if (isDuplicate) return alert("이미 등록된 사업자등록번호입니다."); + } + + const myUUID = getCookie(TOKEN_ID_COOKIE) || FALLBACK_UUID; + + const payload: Record<string, unknown> = { + ctpvCd: sido, + sggCd: sigungu, + dmdCoNm: company.trim(), + dmdInfoId: fullDmdInfoId, + bzmnNo: bizDigits, + zip: zip.trim(), + daddr: mergedAddress, + telno: telDigits, + rgtr: (sessionUser?.rgtr as string) || myUUID, + }; + + if (password) { + payload.dmdAplyPswd = password; + } + + try { + if (editMode) { + payload.mdfr = myUUID; + await putFetch("/reuseFc/request", payload); + + if (typeof window !== "undefined") { + const nextSessionUser = { + ...(sessionUser ?? {}), + ...payload, + }; + delete nextSessionUser.dmdAplyPswd; + sessionStorage.setItem("user", JSON.stringify(nextSessionUser)); + } + + alert("수요처 수정 완료!"); + } else { + await postFetch("/reuseFc/request", payload); + alert("수요처 등록 완료!"); + } + + setPw(""); + setPw2(""); + } catch (error) { + console.error("Failed to submit demand place", error); + alert(editMode ? "수요처 수정에 실패하였습니다." : "수요처 등록에 실패하였습니다."); + } + }; + + const handleSelectAddress = (item: AddrItem) => { + setZip(item.zipNo); + setAddr1(item.roadAddr); + setFocusDetailAddrOnClose(true); + const matchedSido = selectSidoData.find( + (region) => + region.val !== "0" && + isSameRegionName(region.text ?? "", item.ctpvNm ?? ""), + ); + + if (matchedSido) { + setSido(matchedSido.val); + const matchedSigungu = selectSigunData.find( + (region) => + region.val !== "0" && + region.val.startsWith(matchedSido.val) && + isSameRegionName(region.text ?? "", item.sggNm ?? ""), + ); + setSigungu(matchedSigungu?.val ?? ""); + } + + setIsPostcodeModalOpen(false); + }; + + const pageTitle = editMode ? "수요처수정" : "수요처등록"; + + return ( + <main className="bg-white"> + <SubPageHeader + title={pageTitle} + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: pageTitle }, + ]} + /> + + <div className="border border-neutral-200 bg-white"> + <FormRow label="시도" required> + <SelectBox + value={sido} + onChange={(v) => { + setSido(v); + setSigungu(""); + }} + options={sidoOptions} + /> + </FormRow> + + <Divider /> + + <FormRow label="시군구" required> + <select + value={sigungu} + onChange={(e) => setSigungu(e.target.value)} + className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none" + > + {sigunguOptions.map((o) => ( + <option key={o.value} value={o.value}> + {o.label} + </option> + ))} + </select> + </FormRow> + + <Divider /> + + <FormRow label="회사명" required> + <div className="max-w-[420px]"> + <TextInput + value={company} + onChange={setCompany} + placeholder="회사명을 입력해주세요." + /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="사업자등록번호" required> + <div className="max-w-[420px]"> + <TextInput + value={formatBusinessNumber(bizNo)} + onChange={(v) => setBizNo(v.replace(/\D/g, "").slice(0, 10))} + placeholder="사업자 등록번호를 입력해주세요." + /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="종사자등록번호"> + <div className="flex flex-wrap items-center gap-4"> + <div className="w-[420px] max-w-full"> + <TextInput + value={workerNo} + onChange={(v) => setWorkerNo(v.replace(/\D/g, "").slice(0, 4))} + placeholder="종사자등록번호를 입력해주세요." + disabled={noWorkerNo} + /> + </div> + + <label className="flex items-center gap-2 text-sm text-neutral-700"> + <input + type="checkbox" + checked={noWorkerNo} + onChange={(e) => { + setNoWorkerNo(e.target.checked); + if (e.target.checked) setWorkerNo(""); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 해당없음 + </label> + </div> + </FormRow> + + <Divider /> + + <FormRow label="사업자주소" required> + <div className="max-w-[420px] space-y-2"> + <div className="flex gap-2"> + <div className="w-[180px]"> + <TextInput value={zip} onChange={setZip} placeholder="우편번호" /> + </div> + <button + type="button" + onClick={() => setIsPostcodeModalOpen(true)} + className="h-11 rounded bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + > + 우편번호 검색 + </button> + </div> + + <TextInput value={addr1} onChange={setAddr1} placeholder="주소" /> + <TextInput + value={addr2} + onChange={setAddr2} + placeholder="상세 주소를 입력하세요." + inputRef={detailAddrRef} + /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="전화번호" required> + <div className="max-w-[420px]"> + <TextInput + value={tel} + onChange={(v) => setTel(formatKoreanPhoneNumber(v))} + placeholder="전화번호를 입력하세요." + /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="비밀번호" required={!editMode}> + <div className="max-w-[420px]"> + <TextInput + value={pw} + onChange={setPw} + placeholder="비밀번호를 입력하세요." + type="password" + /> + </div> + </FormRow> + + <Divider /> + + <FormRow label="비밀번호 확인" required={!editMode}> + <div className="max-w-[420px]"> + <TextInput + value={pw2} + onChange={setPw2} + placeholder="비밀번호를 한번 더 입력하세요." + type="password" + /> + </div> + </FormRow> + </div> + + <div className="flex justify-center py-12"> + <button + type="button" + onClick={onSubmit} + className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90" + > + {editMode ? "수요처 수정" : "수요처 등록"} + </button> + </div> + + <PostcodeModal + open={isPostcodeModalOpen} + onClose={() => setIsPostcodeModalOpen(false)} + onSelect={handleSelectAddress} + /> + </main> + ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/layout.tsx b/apps/dm_new/app/(with-layout)/apply/layout.tsx new file mode 100644 index 000000000..49c8a6266 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/layout.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; +import Lnb from "@/components/Lnb"; + +export default function ApplyLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb /> + <main className="flex-1 px-[100px] pt-[80px]">{children}</main> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx b/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx new file mode 100644 index 000000000..877eb7d8c --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx @@ -0,0 +1,99 @@ +// apps/dm/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx (수질기준참고 모달) +"use client"; + +import { useEffect, useId } from "react"; +import Image from "next/image"; + +type Props = { + open: boolean; + onClose: () => void; + imageSrc: string; + title?: string; +}; + +export default function WaterQualityRefModal({ + open, + onClose, + imageSrc, + title = "수질 기준 참고", +}: Props) { + const titleId = useId(); + + useEffect(() => { + if (!open) return; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + // 스크롤 잠금 + const prev = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + if (!open) return null; + + return ( + <div className="fixed inset-0 z-[120]"> + {/* Backdrop */} + <button + type="button" + aria-label="닫기" + onClick={onClose} + className="absolute inset-0 bg-black/40" + /> + + {/* Dialog */} + <div className="absolute inset-0 flex items-center justify-center p-4"> + <div + role="dialog" + aria-modal="true" + aria-labelledby={titleId} + className="w-full max-w-[1100px] rounded-[18px] bg-white shadow-2xl overflow-hidden" + > + {/* Header */} + <div className="relative px-10 pt-9 pb-6"> + <button + type="button" + onClick={onClose} + aria-label="닫기" + className="absolute right-7 top-7 h-10 w-10 grid place-items-center rounded-full hover:bg-gray-100" + > + <span className="text-2xl leading-none">×</span> + </button> + + <h2 + id={titleId} + className="text-left text-[28px] font-bold tracking-tight text-gray-900" + > + {title} + </h2> + + <div className="mt-5 h-px w-full bg-gray-200" /> + </div> + + {/* Body (스크롤 영역) */} + <div className="px-10 pb-10"> + <div className="max-h-[70vh] overflow-auto bg-white"> + {/* 이미지가 길어도 여기서 스크롤 */} + <Image + src={imageSrc} + alt="수질 기준 참고" + width={851} + height={2334} + className="block mx-auto max-w-full h-auto" + priority + /> + </div> + </div> + </div> + </div> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/register/page.tsx b/apps/dm_new/app/(with-layout)/apply/register/page.tsx new file mode 100644 index 000000000..3c084bf73 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/register/page.tsx @@ -0,0 +1,500 @@ +// app/(with-layout)/apply/register/page.tsx +/** + * 수요정보등록(수정) 화면 + * + * 🔗 사용 API + * - 수요정보등록: POST /reuseFc/request/demandInfo + * - 수요정보조회: GET /reuseFc/request/' + dmdInfoId - getSdmInfoStatusList + */ +"use client"; + +import { useMemo, useState, useRef } from "react"; +import SubPageHeader from "@/components/SubPageHeader"; +import WaterQualityRefModal from "../modal/WaterQualityRefModal"; +import CandidateSiteModal, { type CandidateSite } from "@/components/CandidateSiteModal"; + +type PHRange = { min: string; max: string }; + +export default function DemandRegisterPage() { + const today = useMemo(() => { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; + }, []); + + const [openRef, setOpenRef] = useState(false); + + const [openCand, setOpenCand] = useState(false); + const [selectedCands, setSelectedCands] = useState<CandidateSite[]>([]); + + // 기본정보 + const [reqDate, setReqDate] = useState<string>(today); + const [reqQty, setReqQty] = useState<string>(""); + const [usePurpose, setUsePurpose] = useState<string>(""); + const [hopePrice, setHopePrice] = useState<string>(""); + + // 요구수질 + const [coliform, setColiform] = useState<string>(""); + const [noDetect, setNoDetect] = useState<boolean>(false); + + const [tn, setTn] = useState<string>(""); + const [residualCl, setResidualCl] = useState<string>(""); + const [tp, setTp] = useState<string>(""); + const [turbidity, setTurbidity] = useState<string>(""); + const [ph, setPh] = useState<PHRange>({ min: "", max: "" }); + const [bod, setBod] = useState<string>(""); + const [chloride, setChloride] = useState<string>(""); + const [odor, setOdor] = useState<string>("불쾌하지 않을 것"); + const [ec, setEc] = useState<string>(""); + const [color, setColor] = useState<string>(""); + const [remark, setRemark] = useState<string>(""); + + // 공급처 후보지 선택(필수아님) + const [cand1, setCand1] = useState(""); + const [cand2, setCand2] = useState(""); + const [cand3, setCand3] = useState(""); + + return ( + <> + + <main className="bg-white"> + + {/* 서브 타이틀 + 브레드크럼 */} + <SubPageHeader + title="수요정보등록" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: "수요정보등록" }, + ]} + /> + + {/* 컨텐츠 영역 */} + <div> + + {/* 기본정보 테이블 */} + <div className="border border-neutral-200 bg-white"> + <FormRow label="요구시기" required> + <DateInput value={reqDate} onChange={setReqDate} /> + </FormRow> + + <Divider /> + + <FormRow label="요구수량(m²/d)" required> + <TextInput value={reqQty} onChange={setReqQty} placeholder="" /> + </FormRow> + + <Divider /> + + <FormRow label="사용용도" required> + <TextInput value={usePurpose} onChange={setUsePurpose} /> + </FormRow> + + <Divider /> + + <FormRow label="희망단가(원)" required> + <TextInput value={hopePrice} onChange={setHopePrice} /> + </FormRow> + </div> + + {/* 요구수질 섹션 타이틀 바 */} + <div className="grid grid-cols-12"> + <div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center"> + <span className="text-lg font-semibold text-neutral-800">요구수질</span> + <button + type="button" + onClick={() => setOpenRef(true)} + className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50" + > + 수질기준 참고 + </button> + {/* 수질기준참고 모달 */} + <WaterQualityRefModal + open={openRef} + onClose={() => setOpenRef(false)} + imageSrc="/images/landing/water_quality_ref_1.png" + /> + </div> + </div> + + {/* 요구수질 테이블 */} + <div className="border border-neutral-200 bg-white"> + {/* 1행 */} + <QualityRow + leftLabel="총대장균군수(개/mg)" + leftRequired + leftControl={ + <div className="flex items-center gap-4"> + <TextInput + value={coliform} + onChange={setColiform} + disabled={noDetect} + className={noDetect ? "bg-neutral-100 text-neutral-400" : ""} + /> + <label className="flex items-center gap-2 text-sm text-neutral-700"> + <input + type="checkbox" + checked={noDetect} + onChange={(e) => { + setNoDetect(e.target.checked); + if (e.target.checked) setColiform(""); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 불검출 + </label> + </div> + } + rightLabel="총질소(T-N)(mg/L)" + rightControl={ + <InlineSuffixInput value={tn} onChange={setTn} suffix="이하" /> + } + /> + <Divider /> + + {/* 2행 */} + <QualityRow + leftLabel="결합잔류염소(mg/L)" + leftRequired + leftControl={<TextInput value={residualCl} onChange={setResidualCl} />} + rightLabel="총인(T-P)(mg/L)" + rightControl={ + <InlineSuffixInput value={tp} onChange={setTp} suffix="이하" /> + } + /> + <Divider /> + + {/* 3행 */} + <QualityRow + leftLabel="탁도(NTU)" + leftRequired + leftControl={ + <InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" /> + } + rightLabel="수소이온농도(pH)" + rightControl={ + <div className="flex items-center gap-2"> + <TextInput + value={ph.min} + onChange={(v) => setPh((p) => ({ ...p, min: v }))} + className="w-32" + /> + <span className="text-neutral-500">~</span> + <TextInput + value={ph.max} + onChange={(v) => setPh((p) => ({ ...p, max: v }))} + className="w-32" + /> + </div> + } + /> + <Divider /> + + {/* 4행 */} + <QualityRow + leftLabel={ + <> + 생물화학적산소요구량 + <br /> + (BOD)(mg/L) + </> + } + leftRequired + leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />} + rightLabel="염화물(mgCL/L)" + rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />} + /> + <Divider /> + + {/* 5행 */} + <QualityRow + leftLabel="냄새" + leftRequired + leftControl={ + <select + value={odor} + onChange={(e) => setOdor(e.target.value)} + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option>불쾌하지 않을 것</option> + <option>약간 있음</option> + <option>있음</option> + </select> + } + rightLabel="전기전도도(µs/cm)" + rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix="이하" />} + /> + <Divider /> + + {/* 6행 */} + <QualityRow + leftLabel="색도(도)" + leftRequired + leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />} + rightLabel="비고" + rightControl={ + <textarea + value={remark} + onChange={(e) => setRemark(e.target.value)} + className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500" + /> + } + /> + <Divider /> + + {/* 후보지 선택 */} + <div className="grid grid-cols-12"> + <LabelCell> + 공급처 후보지 선택 + <br /> + (필수아님) + </LabelCell> + + <div className="col-span-12 md:col-span-10 p-6"> + <div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4"> + <TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" /> + <TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" /> + <TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" /> + + <div className="flex items-center gap-3 xl:ml-2"> + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700" + onClick={() => setOpenCand(true)} + > + 후보지 조회 + </button> + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600" + onClick={() => { + // TODO: 유사사례 연결 + console.log("유사사례"); + }} + > + 유사사례 + </button> + </div> + </div> + </div> + </div> + </div> + + {/* 하단 버튼 */} + <div className="flex justify-center py-12"> + <button + type="button" + className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90" + onClick={() => { + // TODO: 저장 처리 + console.log("수요정보 등록"); + }} + > + 수요정보 등록 + </button> + </div> + + </div> + </main> + + <CandidateSiteModal + open={openCand} + onClose={() => setOpenCand(false)} + initialSelected={selectedCands} + onApply={(list) => { + setSelectedCands(list); + + // ✅ cand1~3 입력칸에 반영(시설명만 넣는 버전) + setCand1(list[0]?.name ?? ""); + setCand2(list[1]?.name ?? ""); + setCand3(list[2]?.name ?? ""); + + setOpenCand(false); + }} + /> + </> + ); +} + +/* ----------------------------- UI helpers ----------------------------- */ + +function Divider() { + return <div className="h-px w-full bg-neutral-200" />; +} + +function LabelCell({ + children, + required, +}: { + children: React.ReactNode; + required?: boolean; +}) { + return ( + <div className="col-span-12 md:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + {children} + {required ? <span className="text-red-500">*</span> : null} + </span> + </div> + ); +} + +function FormRow({ + label, + required, + children, +}: { + label: string; + required?: boolean; + children: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + <LabelCell required={required}>{label}</LabelCell> + <div className="col-span-12 md:col-span-10 p-6">{children}</div> + </div> + ); +} + +function QualityRow({ + leftLabel, + leftRequired, + leftControl, + rightLabel, + rightControl, +}: { + leftLabel: React.ReactNode; + leftRequired?: boolean; + leftControl: React.ReactNode; + rightLabel: React.ReactNode; + rightControl: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + {/* 좌측 라벨 */} + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + <span className="leading-6">{leftLabel}</span> + {leftRequired ? <span className="text-red-500">*</span> : null} + </span> + </div> + + {/* 좌측 입력 */} + <div className="col-span-12 lg:col-span-4 p-6">{leftControl}</div> + + {/* 우측 라벨 */} + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="leading-6">{rightLabel}</span> + </div> + + {/* 우측 입력 */} + <div className="col-span-12 lg:col-span-4 p-6">{rightControl}</div> + </div> + ); +} + +function TextInput({ + value, + onChange, + placeholder, + disabled, + className, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +}) { + return ( + <input + value={value} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className={[ + "h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none", + "focus:border-neutral-500", + disabled ? "cursor-not-allowed" : "", + className ?? "", + ].join(" ")} + /> + ); +} + +function InlineSuffixInput({ + value, + onChange, + suffix, +}: { + value: string; + onChange: (v: string) => void; + suffix: string; +}) { + return ( + <div className="flex items-center gap-3"> + <TextInput value={value} onChange={onChange} className="max-w-[200px]" /> + <span className="text-sm text-neutral-600">{suffix}</span> + </div> + ); +} + +function DateInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + + const ref = useRef<HTMLInputElement | null>(null); + + const openPicker = () => { + const el = ref.current; + if (!el) return; + + // Chrome/Edge 등 지원 + if (typeof (el as any).showPicker === "function") { + (el as any).showPicker(); + return; + } + + // fallback (일부 브라우저) + el.focus(); + el.click(); + }; + return ( + <div className="relative w-full max-w-[200px]"> + <input + ref={ref} + type="date" + value={value} + onChange={(e) => onChange(e.target.value)} + className={[ + "h-11 w-full rounded-md border border-neutral-300 bg-white px-3 pr-10 text-sm outline-none focus:border-neutral-500", + // ✅ 기본 달력 아이콘 숨김 (Tailwind arbitrary selector) + "[&::-webkit-calendar-picker-indicator]:opacity-0", + "[&::-webkit-calendar-picker-indicator]:absolute", + "[&::-webkit-calendar-picker-indicator]:right-0", + "[&::-webkit-calendar-picker-indicator]:w-10", + "[&::-webkit-calendar-picker-indicator]:h-full", + "[&::-webkit-calendar-picker-indicator]:cursor-pointer", + ].join(" ")} + /> + + {/* ✅ 클릭 가능한 아이콘 버튼 */} + <button + type="button" + onClick={openPicker} + className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-neutral-500 hover:text-neutral-700" + aria-label="달력 열기" + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> + <path d="M7.14286 7.99104H16.7812M6.48512 2.5625V4.19126M17.3125 2.5625V4.19106M20.5 7.19106L20.5 18.5625C20.5 20.2194 19.1569 21.5625 17.5 21.5625H6.5C4.84315 21.5625 3.5 20.2194 3.5 18.5625V7.19106C3.5 5.53421 4.84315 4.19106 6.5 4.19106H17.5C19.1569 4.19106 20.5 5.53421 20.5 7.19106Z" stroke="#1A1A1A" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" /> + </svg> + </button> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/apply/status/page.tsx b/apps/dm_new/app/(with-layout)/apply/status/page.tsx new file mode 100644 index 000000000..fc9e61134 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/apply/status/page.tsx @@ -0,0 +1,340 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; +import SubPageHeader from "@/components/SubPageHeader"; +import { useDmStore } from "@/app/store"; +import type { DemandCompBizType } from "@/app/store/sdmMng"; + +type TopField = { label: string; value: string; required?: boolean }; +type PairField = { label: string; value: string }; +type SessionUser = { dmdInfoId?: string }; + +const formatDate = (value: string | Date | "" | undefined) => { + if (!value) return "-"; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return String(value); + const yyyy = date.getFullYear(); + const mm = String(date.getMonth() + 1).padStart(2, "0"); + const dd = String(date.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; +}; + +const formatValue = (value: string | number | null | undefined) => { + if (value === null || value === undefined || value === "") return "-"; + return String(value); +}; + +const formatBelow = (value: string | number | null | undefined) => { + if (value === null || value === undefined || value === "") return "-"; + if (Number(value) === -1) return "불검출"; + return `${value} 이하`; +}; + +function StatusBadge({ v }: { v: string }) { + const cls = v === "신청" ? "text-red-600" : "text-lime-600"; + return <span className={["font-medium", cls].join(" ")}>{v || "-"}</span>; +} + +export default function DemandStatusPage() { + const { getSdmInfoStatusList, Sdm, Common } = useDmStore((state) => state); + const { successLogin, sdmInfoStatusList } = Sdm; + const { selectSidoData, selectSigunData } = Common; + + const detailRef = useRef<HTMLDivElement | null>(null); + + const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); + const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState<number | "" | null>(null); + const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState<DemandCompBizType[]>([]); + + useEffect(() => { + const user = sessionStorage.getItem("user"); + if (user) { + setIsLoginStorageInfo(true); + } + }, []); + + useEffect(() => { + if (!successLogin && !isLoginStorageInfo) return; + + const user = sessionStorage.getItem("user"); + if (!user) return; + + try { + const userData = JSON.parse(user) as SessionUser; + const dmdInfoId = userData.dmdInfoId?.trim(); + if (dmdInfoId) { + getSdmInfoStatusList(dmdInfoId); + } + } catch (error) { + console.error("Error parsing user data:", error); + } + }, [successLogin, isLoginStorageInfo, getSdmInfoStatusList]); + + useEffect(() => { + const mappedList = sdmInfoStatusList.map((item) => ({ + ...item, + ctpvCd: selectSidoData.find((sido) => sido.val === item.ctpvCd?.trim())?.text || item.ctpvCd, + sggCd: selectSigunData.find((sigun) => sigun.val === item.sggCd?.trim())?.text || item.sggCd, + })); + + setTransformedSdmInfoStatusList(mappedList); + }, [sdmInfoStatusList, selectSidoData, selectSigunData]); + + useEffect(() => { + const first = transformedSdmInfoStatusList[0]; + if (!first) { + setSelectedDmdInfoSn(null); + return; + } + + if (selectedDmdInfoSn == null) { + setSelectedDmdInfoSn(first.dmdInfoSn); + return; + } + + const exists = transformedSdmInfoStatusList.some( + (item) => item.dmdInfoSn === selectedDmdInfoSn, + ); + if (!exists) { + setSelectedDmdInfoSn(first.dmdInfoSn); + } + }, [transformedSdmInfoStatusList, selectedDmdInfoSn]); + + const selectedSdmInfoStatus = useMemo(() => { + if (!transformedSdmInfoStatusList.length) return null; + return ( + transformedSdmInfoStatusList.find((item) => item.dmdInfoSn === selectedDmdInfoSn) ?? + transformedSdmInfoStatusList[0] + ); + }, [transformedSdmInfoStatusList, selectedDmdInfoSn]); + + const topFields: TopField[] = useMemo(() => { + if (!selectedSdmInfoStatus) return []; + + return [ + { label: "요구시기", value: formatDate(selectedSdmInfoStatus.dmndTmng), required: true }, + { label: "요구수량(㎥/d)", value: formatValue(selectedSdmInfoStatus.dmndQty) }, + { label: "사용용도", value: formatValue(selectedSdmInfoStatus.useUsgCd) }, + { label: "희망단가(원/㎥)", value: formatValue(selectedSdmInfoStatus.wntUntprc) }, + ]; + }, [selectedSdmInfoStatus]); + + const waterFields: PairField[] = useMemo(() => { + if (!selectedSdmInfoStatus) return []; + + return [ + { label: "총대장균군수(개/100mL)", value: formatBelow(selectedSdmInfoStatus.grssClfg) }, + { label: "결합잔류염소(mg/L)", value: formatBelow(selectedSdmInfoStatus.combResdch) }, + { label: "탁도(NTU)", value: formatBelow(selectedSdmInfoStatus.trbt) }, + + { label: "생물학적산소요구량(BOD)(mg/L)", value: formatBelow(selectedSdmInfoStatus.blgcOxygDmndQnt) }, + { label: "냄새", value: formatValue(selectedSdmInfoStatus.smll) }, + { label: "색도(도)", value: formatBelow(selectedSdmInfoStatus.crmty) }, + + { label: "총질소(T-N)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tn) }, + { label: "총인(T-P)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tph) }, + { label: "수소이온농도(pH)", value: formatValue(selectedSdmInfoStatus.ph) }, + + { label: "염화물(mgCL/L)", value: formatBelow(selectedSdmInfoStatus.clIonQnt) }, + { label: "전기전도도(μS/cm)", value: formatBelow(selectedSdmInfoStatus.cdtnv) }, + { label: "비고", value: formatValue(selectedSdmInfoStatus.rmrk) }, + ]; + }, [selectedSdmInfoStatus]); + + const candidateNames = useMemo(() => { + const list = selectedSdmInfoStatus?.splyCndtList ?? []; + return [ + list[0]?.ruseUtztnFcltNm ?? "", + list[1]?.ruseUtztnFcltNm ?? "", + list[2]?.ruseUtztnFcltNm ?? "", + ]; + }, [selectedSdmInfoStatus]); + + const onSelectSdmInfoStatus = (dmdInfoSn: number | "") => { + setSelectedDmdInfoSn(dmdInfoSn); + requestAnimationFrame(() => { + detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + }; + + return ( + <main className="bg-white"> + <SubPageHeader + title="수요신청현황" + crumbs={[ + { label: "Home", href: "/main" }, + { label: "수요신청", href: "/apply/status" }, + { label: "수요신청현황" }, + ]} + hideFooterLine + hideGuideText + /> + + <div> + <section className="mt-6"> + <div className="text-2xl font-bold text-slate-700">수요정보 신청현황</div> + + <div className="mt-3 border border-neutral-200"> + <table className="w-full table-fixed"> + <thead className="bg-[#E7F4E2]"> + <tr className="text-sm text-slate-700"> + <th className="w-[60px] px-3 py-3 text-center">NO.</th> + <th className="w-[120px] px-3 py-3 text-center">시도</th> + <th className="w-[120px] px-3 py-3 text-center">시군구</th> + <th className="w-[180px] px-3 py-3 text-center">회사명</th> + <th className="px-3 py-3 text-center">공장주소</th> + <th className="w-[140px] px-3 py-3 text-center">전화번호</th> + <th className="w-[120px] px-3 py-3 text-center">신청일자</th> + <th className="w-[110px] px-3 py-3 text-center">진행상태</th> + </tr> + </thead> + + <tbody className="bg-white text-base text-slate-700"> + {transformedSdmInfoStatusList.length === 0 ? ( + <tr> + <td colSpan={8} className="px-3 py-10 text-center text-slate-400"> + 조회된 신청내역이 없습니다. + </td> + </tr> + ) : ( + transformedSdmInfoStatusList.map((sdmInfoStatus, index) => { + const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn; + return ( + <tr + key={`${sdmInfoStatus.dmdInfoSn}-${index}`} + role="row" + tabIndex={0} + onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn); + } + }} + className={[ + "border-t border-neutral-200 outline-none", + "cursor-pointer", + active ? "bg-slate-50" : "hover:bg-slate-50", + "focus-visible:ring-2 focus-visible:ring-slate-300", + ].join(" ")} + aria-selected={active} + > + <td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td> + <td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td> + <td className="px-3 py-3 text-center"> + <StatusBadge v={formatValue(sdmInfoStatus.status)} /> + </td> + </tr> + ); + }) + )} + </tbody> + </table> + </div> + </section> + + <section className="mt-10 pb-16" ref={detailRef}> + <div className="flex justify-between items-center"> + <div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div> + <button + type="button" + className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + > + 수정 + </button> + </div> + + {!selectedSdmInfoStatus ? ( + <div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400"> + 상세 정보를 표시할 신청건이 없습니다. + </div> + ) : ( + <div className="mt-3 border border-neutral-200"> + <div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수량 + </div> + + <div className="grid grid-cols-12 text-sm"> + {topFields.map((field) => ( + <div + key={field.label} + className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {field.label} + {field.required ? <span className="ml-1 text-red-500">*</span> : null} + </div> + <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center"> + {field.value} + </div> + </div> + ))} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수질 + </div> + + {waterFields.map((field) => ( + <div + key={field.label} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {field.label} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {field.value} + </div> + </div> + ))} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 공급처 후보지 + </div> + + {candidateNames.map((candidateName, index) => ( + <div + key={`candidate-${index}`} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 후보지{index + 1} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {candidateName} + </div> + </div> + ))} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200"> + 수요신청 진행상태 + </div> + + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 진행상태 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {formatValue(selectedSdmInfoStatus.status)} + </div> + </div> + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 공급처매칭결과 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)} + </div> + </div> + </div> + </div> + )} + </section> + </div> + </main> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/australia/page.tsx b/apps/dm_new/app/(with-layout)/example/globalCase/australia/page.tsx new file mode 100644 index 000000000..7766aa62f --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/globalCase/australia/page.tsx @@ -0,0 +1,151 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/australia/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "호주", href: "/reuse/business/globalCase/australia" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/australia_01.png", alt: "호주 사례 1" }, + ]} + /> + + {/* 생활용수로 재이용수의 적극 활용 */} + <section className="mt-8"> + <SectionTitle>생활용수로 재이용수의 적극 활용</SectionTitle> + <div className="px-4 mt-6"> + <ul className="space-y-4 text-base text-[#4F4F4F]"> + <li> + – 재이용수를 공원, 골프장 등의 관개용수로 공급(초록색 지역) + </li> + <li> + – Dual pipe로 재이용수를 특정 주거단지로 공급(보라색 지역) + </li> + <li> + – 현재 7,000여 가구가 재이용수를 사용 + </li> + <li> + – 2030년까지 43,000가구로 확대 예정 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 호주 재이용수 광역 활용(퀸즈랜드주 브리즈번) */} + <section className="mt-6"> + <SectionTitle>호주 재이용수 광역 활용(퀸즈랜드주 브리즈번)</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망 + </li> + <li> + • 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급<br /> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li> + <li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li> + </ul> + </li> + <li> + • 12개 댐, 32개 수처리 시설, 3개 재이용시설, 담수화 플랜트 1개, 저수지 28개와 600km 이상의 물 공급 관로를 건설 + </li> + <li> + • 지역적으로 강수량이 고르지 않아, 댐의 저수율이 불균등할 때 지역의 물 공급을 보충하는 중요한 역할을 수행 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주는 2000~2009년 강수량 감소로 밀레니엄 가뭄이 발생하여 심각한 물 부족 현상이 발생함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주는 해안 도시에 인구가 밀집하여 거주하고 있으며 인구 증가율이 타 선진국 대비 높아, 향후 도시에 공급할 수자원 확보가 필요함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주정부는 1990년대부터 하·폐수가 방류될 경우의 환경 피해에 대해 인식하기 시작하고 배출규제와 위원회를 구성하기 시작</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>연방정부는 주에 인센티브를 주는 방식으로 물 정책을 장려하고 토지소유권과 물 권리를 구분하여 거래를 허용하고 물 재이용 활성화를 위한 Water Smart Australia 프로그램을 시행</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>호주의 용수공급 체계는 영국과 미국의 특성들이 존재하지만, 주의 특성과 주 정부의 정책 방향에 따라 주마다도 많은 차이가 있음. 특히 대부분의 주에서 수 자원 개발 기능과 수도 공급의 기능을 분리하여 주정부<br/>산하 기관에서 운영하는게 특징</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/eu/page.tsx b/apps/dm_new/app/(with-layout)/example/globalCase/eu/page.tsx new file mode 100644 index 000000000..e2930b392 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/globalCase/eu/page.tsx @@ -0,0 +1,142 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/eu/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[EU] 법률을 제정, EU국가에서 물 재이용 유도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "EU", href: "/reuse/business/globalCase/eu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/eu_01.png", alt: "EU 사례 1" }, + ]} + /> + + {/* 물 재이용에 대한 법률을 제정(2020 제정, 2023 시행) */} + <section className="mt-8"> + <SectionTitle>물 재이용에 대한 법률을 제정(2020 제정, 2023 시행)</SectionTitle> + <div className="px-4 mt-6"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741) + </li> + <li> + • 2023년부터 모든 EU국가에서 시행 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 유럽연합 회원국 물 재이용 지원정책 */} + <section className="mt-6"> + <SectionTitle>유럽연합 회원국 물 재이용 지원정책</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 유럽연합 차원에서 다양한 물 재이용 프로그램 자금 지원<br /> + <ul className="list-inside space-y-4 text-[#767676] text-sm ml-4 mt-2"> + <li>– 2021년부터 3년간 Life RECYCLIO 프로그램 지원 → 세부내용 물 재이용 프로그램<br/> + <ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2"> + <li>1. 환경 관련 사업에 자금을 지원하는 Life 프로그램의 일환으로 80만 유로를 지원</li> + <li>2. 식품·농업 분야 물 재이용 확대를 위해 기술 이전 및 평가 실시</li> + </ul> + </li> + <li>– 물 재이용 정책 수행을 위해 BOOST-INN 180만 유로를 지원<br/> + <ul className="list-inside space-y-2 text-[#767676] ml-4 mt-2"> + <li> *프랑스, 이탈리아, 스페인 등 유럽 선진국 솔루션 공유</li> + </ul> + </li> + </ul> + </li> + <li> + • EU산하 JRC(공동연구센터)에서 물 재이용 기술제공 지원<br /> + <ul className="list-inside space-y-2 text-[#767676] text-sm ml-4 mt-2"> + <li>‘물 재이용 회원 관리 실무 커뮤니티(Water Reuse Management Community of Practice)’ 시범사업 수행</li> + <li>유럽 전역에 성공적인 모델 활용 및 혁신적인 물 재이용 솔루션을 확장할 수 있는 기술을 제공</li> + </ul> + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2019년 기준 EU 인구의 38%가 물 부족의 영향을 받을 것으로 예상됨. 이 흐름대로면 2030년까지 유럽 유역 전반에 물 부족 문제가 발생하게 될 것으로 예상. 인구 증가, 도시화의 확대, 염분화 및 기후변화들이 수자원 문제들의 주요 요인이며, 식습관 변화와 산업용수 수요 증가 등으로 악화되고 있는 상황</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>유럽의 물 부족 상황은 지역에 따라 다르게 나타나 스페인을 비롯한 남부유럽 에서 특히 심각함. 전반적으로 북유럽에 비해 남유럽의 물 스트레스가 심하며, 특히 스페인과 포르투갈, 프랑스 동부지역의 물 스트레스가 심각함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>유럽 연합에서도 물 부족 문제를 인식하고 2000년 EU Water Framework Directive을 개발, 이후 2020년 재이용에 관한 법률을 제정</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/overview/page.tsx b/apps/dm_new/app/(with-layout)/example/globalCase/overview/page.tsx new file mode 100644 index 000000000..4d84b0c43 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/globalCase/overview/page.tsx @@ -0,0 +1,229 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/overview/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="해외 재이용 사례 개요" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "사례개요", href: "/reuse/business/globalCase/overview" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/overview_01.png", alt: "해외 재이용 사례 1" }, + ]} + /> + + <section className="mt-6"> + <div className="rounded-md bg-[#e9f0f1] p-8"> + <h3 className="text-xl font-bold text-gray-800 leading-relaxed"> + 2024년 하수도 통계 기준 15.6%이며,<br /> + 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 <span className="font-extrabold">17%</span> + </h3> + + {/* 진행 바 영역 */} + <div className="mt-6 space-y-4"> + {/* 2024 */} + <div className="flex items-center gap-4"> + <span className="text-sm text-gray-600">’24년도</span> + <div className="relative h-7 rounded-r-lg"> + <div className="h-7 w-[112px] rounded-r-lg bg-gray-400" /> + </div> + <span className="text-sm text-gray-700">15.6%</span> + </div> + + {/* 2030 */} + <div className="flex items-center gap-4"> + <span className="text-sm text-gray-600">’30년도</span> + <div className="relative"> + <div className="h-10 w-[327px] rounded-r-lg bg-gradient-to-r from-sky-500 to-green-500" /> + </div> + <span className="text-xl font-bold text-gray-900">17%</span> + </div> + </div> + </div> + + {/* 설명 문구 */} + <ul className="mt-6 space-y-2 text-base text-gray-600"> + <li> + • 우리나라의 하수 처리 재이용률은 <span className="text-sky-600 ">15% 수준</span> + </li> + <li> + • 대만은 가뭄과 공업용수 증가에 대응하여 + <span className="text-sky-600 "> + {" "}현재 4.3% 수준이며, 16.5%까지 확대 계획 수립 + </span> + </li> + </ul> + + {/* 테이블 */} + <div className="mt-6"> + <table className="w-full text-center text-sm"> + <thead> + <tr> + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">해외정보</th> + <th className="bg-teal-500 px-4 py-3 text-white rounded-t-lg">현재</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">미래(목표, 예측치)</th> + </tr> + </thead> + <tbody className="bg-white border border-t-0"> + <tr className="border-t"> + <td className="px-4 py-3 font-medium">한국</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">12.1%</td> + <td className="px-4 py-3">15.6%</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">17%</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 font-medium">대만</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">2.4%</td> + <td className="px-4 py-3">4.3%</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">16.5%</td> + </tr> + </tbody> + </table> + </div> + </section> + + <Hr className="my-11" /> + + {/* 주요 국가의 하수처리수 재이용률 (%) */} + <section className="pb-[200px]"> + <SectionTitle>주요 국가의 하수처리수 재이용률 (%)</SectionTitle> + {/* 설명 문구 */} + <ul className="mt-6 space-y-2 text-base text-gray-600"> + <li> + • 전 세계적으로 물 재이용은 호주, 유럽, 아프리카, 미국 등 가뭄이 잦거나 심각한 가뭄이 발생한 국가에서 많이 이루어짐 + </li> + <li> + • 이스라엘, 쿠웨이트 등 중동 및 북아프리카 지역은 가용 수자원이 제한되어 있어 재이용량 비중도 매우 높은 편 + </li> + </ul> + {/* 테이블 (2단 병렬: 좌/우 데이터) */} + <div className="mt-6"> + <div className="overflow-x-auto"> + <table className="min-w-[900px] w-full text-center text-sm"> + <thead> + <tr> + {/* 좌측 3컬럼 */} + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th> + + {/* 우측 3컬럼 */} + <th className="bg-[#C2CAD2] px-4 py-3 text-black rounded-t-lg">국가명</th> + <th className="bg-sky-600 px-4 py-3 text-white rounded-t-lg">지역</th> + <th className="bg-green-500 px-4 py-3 text-white rounded-t-lg">재이용률</th> + </tr> + </thead> + + <tbody className="bg-white border border-t-0"> + {[ + { + L: { c: "이스라엘", r: "중동&북아프리카", v: "92.3" }, + R: { c: "튀르키예", r: "동유럽&중앙아시아", v: "26.6" }, + }, + { + L: { c: "쿠웨이트", r: "중동&북아프리카", v: "89.3" }, + R: { c: "독일", r: "서유럽", v: "25.0" }, + }, + { + L: { c: "호주", r: "동아시아&태평양", v: "49.0" }, + R: { c: "스페인", r: "서유럽", v: "24.4" }, + }, + { + L: { c: "싱가포르", r: "동아시아&태평양", v: "44.4" }, + R: { c: "한국", r: "동아시아&태평양", v: "12.1" }, + }, + { + L: { c: "그리스", r: "서유럽", v: "39.1" }, + R: { c: "미국", r: "북아메리카", v: "13.1" }, + }, + { + L: { c: "사우디아라비아", r: "중동&북아프리카", v: "32.8" }, + R: { c: "프랑스", r: "서유럽", v: "9.5" }, + }, + { + L: { c: "이탈리아", r: "서유럽", v: "31.7" }, + R: { c: "대만", r: "동아시아&태평양", v: "2.4" }, + }, + { + L: { c: "룩셈부르크", r: "서유럽", v: "29.1" }, + R: { c: "일본", r: "동아시아&태평양", v: "0.8" }, + }, + { + L: { c: "포르투갈", r: "서유럽", v: "29.0" }, + R: { c: "", r: "", v: "" }, // 마지막 우측은 빈칸(이미지처럼) + }, + ].map((row, idx) => ( + <tr key={idx} className="border-t"> + {/* 좌측 */} + <td className="px-4 py-3 font-medium text-gray-700">{row.L.c}</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">{row.L.r}</td> + <td className="px-4 py-3 bg-green-50 text-gray-700">{row.L.v}</td> + + {/* 우측 */} + <td className="px-4 py-3 font-medium text-gray-700">{row.R.c}</td> + <td className="px-4 py-3 bg-sky-50 text-gray-700">{row.R.r}</td> + <td className="px-4 py-3 bg-green-50 text-gray-700">{row.R.v}</td> + </tr> + ))} + </tbody> + </table> + </div> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/singapore/page.tsx b/apps/dm_new/app/(with-layout)/example/globalCase/singapore/page.tsx new file mode 100644 index 000000000..3d8f35836 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/globalCase/singapore/page.tsx @@ -0,0 +1,172 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/singapore/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[싱가포르] 수요가 높은 산업의 물 재이용 의무화" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "싱가포르", href: "/reuse/business/globalCase/singapore" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/singapore_01.png", alt: "싱가포르 사례 1" }, + ]} + /> + + {/* 싱가포르 NEWater 비중 확대 */} + <section className="mt-8"> + <SectionTitle>싱가포르 NEWater 비중 확대</SectionTitle> + <div className="px-4 mt-6"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설 + </li> + <li> + • NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용 + </li> + <li> + • 2060년까지 NEWater 비중 55%까지 높이는 것이 싱가포르의 목표 + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 싱가포르 재이용수 의무화 및 지원정책 */} + <section className="mt-6"> + <SectionTitle>싱가포르 재이용수 의무화 및 지원정책</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 공업용수 수요가 높은 산업에 대해 물 재이용을 의무화<br /> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>– 반도체업계 전체 비 재이용수(non-domestic용수) 17% 차지 → 공업용수 수요 원인</li> + <li>– 2024년 1월 1일부터 연간 최소 60,000㎥를 소비하는 사업자는 재이용수 사용 의무화</li> + <li>– 반도체 공장의 경우 최소 50% 이상의 재이용수를 요구함(현재는 43% 정도)</li> + </ul> + </li> + <li> + • 재정 지원을 통해 재이용 활성화를 지원<br /> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>– 재이용 설비를 기준으로 2025년까지 싱가포르 수자원공사의 2가지 금융상품 제공<br/> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>1. Water Efficiency Fund</li> + <li>2. Industrial Water Solutions Demonstration Fund</li> + </ul> + </li> + <li className="mt-2">– 2025년 기준 지원 한도 상향 → 물 재이용 활성 도모<br/> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>투입비용 50% → 75% 지원 / WSDP: $400만 → $500만</li> + <li>물 효율이 높은 장비를 도입하는 데에도 최대 $300만 지원 가능</li> + </ul> + </li> + </ul> + </li> + </ul> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 천연 수자원이 많지 않아, 말레이시아 조호르와 1962년 맺은 협약으로 물을 수입해왔으며, 재이용수를 늘리기 위한 노력을 계속하고 있음<br/> + <ul className="list-inside text-[#767676] text-sm ml-4 mt-2"> + <li>- 싱가포르는 국토 내에 호수와 하천이적고 집수공간도 적어 빗물로 확보할 수 있는 수자원량은 전체 물 수요의 20%에 불과함</li> + </ul> + </li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 도심에 저수지를 건설하 고 건물에 빗물이용시설을 건설하여 빗물을 재이용하고 있음</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>싱가포르는 2002년부터 하수재이용수인 NEWater를 개발, 비음 용수와 간접음용수로 활용하고 있음<br/> + <ul className="list-inside text-[#767676] text-sm/6 ml-4 mt-2"> + <li>- 재이용수를 NEWater라는 이름으로 명명하고 NEWater에 대한 신뢰 확보를 위해 NEWater 방문자 센터도 건설함</li> + <li>- 비음용수로 많이 사용하며 건기에는 NEWater를 저수지에 추가하여 원수와 섞음. 원수는 상수도에서 처리 한 후 가정에 수돗물로 공급함</li> + <li>- NEWater는 전용 파이프를 통해 주로 웨이퍼 제조 공장, 산업단지, 상업용 건물에서 산업 및 냉각수로 사용. 제일 많이 사용하는 곳은 웨이퍼 제조 공장</li> + </ul> + </li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2024년 4월 기준 1㎥ 당 공업용수 가격은 $1.66이며, Non-domestic 기준 NEWater 가격은 $2.41</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>공업용수 가격 $1.66은 수도요금(water tariff)과 수자원세(waterborne tax)로 구분됨. $1.66에는 $1만큼의 수자원세가 포함됨</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>2023년 싱가포르는 산업용수 수요가 높은 산업에 대해 물 재 이용을 의무화했으며, 정부는 기금을 통해 물 재이용을 지원함</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/taiwan/page.tsx b/apps/dm_new/app/(with-layout)/example/globalCase/taiwan/page.tsx new file mode 100644 index 000000000..91f7b68af --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/globalCase/taiwan/page.tsx @@ -0,0 +1,216 @@ +// apps/dm/app/(landing)/reuse/business/globalCase/taiwan/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="[대만] 국가 주도의 공업용수 공급" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "해외 재이용 사례" }, + { label: "대만", href: "/reuse/business/globalCase/taiwan" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/globalCase/taiwan_01.png", alt: "대만 사례 1" }, + ]} + /> + + {/* 재생 수자원 개발법 제정(2015년제정, 2022개정) */} + <section className="mt-8"> + <SectionTitle>재생 수자원 개발법 제정(2015년제정, 2022개정)</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-2 text-base text-gray-600"> + <li> + • 목적: 수자원 지속 가능성 향상, 재생 수자원 활용을 통해 물부족 문제 해결 + </li> + <li> + • 내용: 재생 수자원 정의·분류, 이용 의무, 이용 제한, 수질 기준 등 + </li> + </ul> + <div className="rounded-sm bg-[#e9f0f1] p-8 mt-5"> + <ul className="space-y-1 text-2xl text-[#1A1A1A]"> + <li> + – 제4조. 물 소비 계획에 따른 재이용수 이용 관련 내용 (일정 비율 재이용수 사용 규정) + </li> + <li> + – 제5조. 재이용수의 운영자, 사용자 관련 내용 (지자체에서 하수 무상공급 제공 가능) + </li> + <li> + – 제7조. 재이용수 이용 범위, 이용량 재이용수 수질 기준 내용 (식품, 제약산업 사용 불가) + </li> + <li> + – 제8조. 재이용수 사업 주체, 책임 등에 관한 내용 (광역인 경우, 정부가 지자체 지정) + </li> + </ul> + </div> + </div> + </section> + + <Hr className="my-11" /> + + {/* 대만정부 하수재이용 사업 시행 */} + <section className="mt-6"> + <SectionTitle>대만정부 하수재이용 사업 시행</SectionTitle> + <div className="px-4"> + <ul className="mt-6 space-y-4 text-base text-gray-600"> + <li> + • 대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑)<br /> + <ul className="list-inside text-[#767676] ml-4 mt-2"> + <li>– 1,618억의 사업비 중 92% 국비, 지방비 8% 투입</li> + <li>– 재이용수는 24km 관로로 타이난 TSMC로 공급</li> + </ul> + </li> + <li> + • 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능 + </li> + <li> + • 공급단가는 수질과 운영조건에 따라 다르게 책정 + </li> + </ul> + {/* 테이블 (2단 병렬: 좌/우 데이터) */} + <div className="mt-3"> + <div className="overflow-x-auto"> + <p className="text-center text-2xl py-2">〈대만 재이용 시설별 재이용수 공급단지〉</p> + <table className="min-w-[900px] w-full text-center text-sm"> + <thead className="border"> + <tr> + <th className="bg-[#C2CAD2] px-4 py-3 text-black">사업</th> + <th className="bg-sky-600 px-4 py-3 text-white">가격(NTD/㎥)</th> + <th className="bg-green-500 px-4 py-3 text-white">가격(원/㎥)</th> + </tr> + </thead> + + <tbody className="bg-white text-[#4F4F4F] border border-t-0"> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Fongshan(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">23.39</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,038원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Linhai(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">19.28</td> + <td className="px-4 py-3 bg-[#E7F4E2]">858원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Anping(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">38.28</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,699원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Yongkang(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">23.67</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,050원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Rende(타이난)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">28.26</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,254원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Shuinan(타이중)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">15.00</td> + <td className="px-4 py-3 bg-[#E7F4E2]">668원</td> + </tr> + <tr className="border-t"> + <td className="px-4 py-3 bg-white">Quaitou(가오슝)</td> + <td className="px-4 py-3 bg-[#EAF5F5]">35.44</td> + <td className="px-4 py-3 bg-[#E7F4E2]">1,573원</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <ul className="mt-6 space-y-4 pl-4 text-base text-gray-600"> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 풍부한 강수량에도, 지형적 특성으로 인해 수자원 확보에 어려움을 겪고 있으며, 대만 남부지역의 경우 우기와 건기 사이의 강수량 차이가 큼</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 최근 심각한 가뭄이 여러 해에 걸쳐서 발생하고 있으며, 강수량 감소와 함께 대만 정부 주도하에 반도체 산업을 국가 핵심 산업으로 선정하고 반도체 산업 육성 계획을 세움. 그 결과, 2023년 파운드리 시장의 68.3%를 차지하는 반도체 강국으로 성장했으며 이로 인한 공업용수 수요가 크게 증가</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>반도체 산업은 물 소비가 많아, 안정적인 용수공급이 매우 중요한데 현재 19개의 반도체 공장이 물이 부족한 대만 남부지역에 위치함. 2021년 가뭄 발생 당시, 산업단지 기업들도 용수 공급에 어려움을 겪음. 이는 대만 남부 지역의 물 부족, 물 분배 등에 대한 문제를 야기함</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만 정부는 산업단지 기업들에게 2023년부터 물에 대한 추가요금 부과를 시작했으며, 농업용수에 대해서는 강력한 억제정책을 시행함. 안정적인 용수 공급을 위해서 정부와 기업에서 자체적으로 재이용수 시설에 대한 투자가 이루어지고 있음.</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 2004년 기후변화로 인한 물 부족 상황을 해결하기 위해 수자원 보호 전략을 승인하고, 2015년 대만 환경부는 재생 수자원 개발법을 발표, 2022년에는 한 차례 개정</li> + </ul> + </li> + <li> + <ul className="list-disc list-outside pl-6 space-y-1 leading-relaxed"> + <li>대만은 물 소비량이 많은 산업(high water consuming industries)과 하수처리장(municipal wastewater treatment plants), 두 곳을 재이용수의 주요 공급원으로 계획</li> + </ul> + </li> + </ul> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/layout.tsx b/apps/dm_new/app/(with-layout)/example/layout.tsx new file mode 100644 index 000000000..f117d6792 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/layout.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; +import Lnb from "@/components/Lnb"; + +export default function BusinessLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb /> + <main className="flex-1 px-[100px] pt-[80px]">{children}</main> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx b/apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx new file mode 100644 index 000000000..a15b86cf4 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx @@ -0,0 +1,93 @@ +// apps/dm/app/(landing)/reuse/business/rain/suwon/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="경기도 수원시 빗물이용과 중수도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "빗물 재이용 사례" }, + { label: "수원시", href: "/reuse/business/rain/suwon" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/rain/rain_01.png", alt: "수원시 하수 재이용 1" }, + { src: "/images/landing/business/rain/rain_02.png", alt: "수원시 하수 재이용 2" }, + { src: "/images/landing/business/rain/rain_03.png", alt: "수원시 하수 재이용 3" }, + { src: "/images/landing/business/rain/rain_04.png", alt: "수원시 하수 재이용 4" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 경기도 수원시 빗물이용과 중수도 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 방식 */} + <section className="pb-[200px]"> + <SectionTitle>사업 방식</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + (빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로 비산먼지 저감<br/> + ※ 시설 가동중 도로 노면 온도의 7.2 ~ 26.3℃ 감소 효과 확인(‘21.7월, 수원시청).<br/> + 다만, 가뭄기나 강우량이 적을 경우 빗물을 원활히 공급하기 다소 곤란<br/><br/> + - (중수도) 수원시청, 장안구청, 월드컵경기장에 빗물 연계 중수도를 화장실·조경용수 등으로 사용하여 수도요금 절감(연 7백~30백만원) + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx new file mode 100644 index 000000000..2a6b41045 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx @@ -0,0 +1,111 @@ +// apps/dm/app/(landing)/reuse/business/sewage/bolyeong/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="충남보령 하수 재이용" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "충청남도 보령시", href: "/reuse/business/sewage/bolyeong" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/bolyeong_01.png", alt: "보령시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 충남 보령 하수재이용<br/> + : 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 방식 */} + <section className="mt-6"> + <SectionTitle>사업 방식</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결 + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 처리공정 : 전처리 UF + 후처리 RO<br/> + 시설용량 : 1만m³/일<br/> + 관로 : 8.7km<br/> + 사업비 : 315억원<br/> + 위치: 충청남도 보령시 대청방조제로 322 일원 + </p> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU(환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕으로 추진 되어 경제성보다 공익성을 바탕으로<br/> + 국가전력 기반시설의 안정적 운영 도모 + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx new file mode 100644 index 000000000..0d648f6b7 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx @@ -0,0 +1,94 @@ +// apps/dm/app/(landing)/reuse/business/sewage/cheongju/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="청주시 하수 재이용 시설" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "청주시", href: "/reuse/business/sewage/cheongju" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/cheongju_01.png", alt: "청주시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 청주시 하수 재이용 시설 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="pb-[200px]"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 청주시 하수처리수 재이용 민간투자사업(BTO)<br/> + 공급량 : 35,000㎥/일<br/> + 공급관로 : 8.3㎞<br/> + 사업비 : 622억원<br/> + 수요처 : SK하이닉스 등 일반산업단지<br/> + 지역 (위치) : 충청북도 청주시 흥덕구 가락리 135-9번지 일원<br/> + 처리용량 : 35.000(m³/일)<br/> + 적용기술 : 분리막 (UF : 전처리), (RO : 주처리) + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx new file mode 100644 index 000000000..7efa38116 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx @@ -0,0 +1,148 @@ +// apps/dm/app/(landing)/reuse/business/sewage/guri/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="구리시 도로자동청소시스템" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "구리시", href: "/reuse/business/sewage/guri" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/guri_01.png", alt: "구리시 도로자동청소시스템 1" }, + { src: "/images/landing/business/sewage/guri_02.png", alt: "구리시 도로자동청소시스템 2" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 구리시 도로자동청소시스템 설치사업 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업목적 */} + <section className="mt-6"> + <SectionTitle>사업목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용하여 미세먼지 저감 및 폭염 취약성 완화 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : 61억원<br/> + 위 치 : 구리시 경춘로 및 갈매중앙로 일대 등<br/> + 관 로 : 5.8km<br/> + 공급계획<br/> + </p> + </section> + + <Hr className="my-11" /> + + {/* 공급계획 */} + <section className="pb-[200px]"> + <SectionTitle>공급계획</SectionTitle> + {/* 데이터 테이블 */} + <div className="overflow-x-auto mt-8"> + <table className="w-full border border-[#E3E7EA] text-center"> + <thead> + <tr> + <th className="bg-[#01ACE5] text-white font-semibold py-4 border border-gray-300"> + 시설명 + </th> + <th className="bg-[#33B5C8] text-white font-semibold py-4 border border-gray-300"> + 재이용수 용도 + </th> + <th className="bg-[#55BCA2] text-white font-semibold py-4 border border-gray-300"> + 공급량(㎥/일) + </th> + <th className="bg-[#6DBB4F] text-white font-semibold py-4 border border-gray-300"> + 원수 + </th> + </tr> + </thead> + <tbody> + <tr> + <td + className="bg-white py-6 border border-gray-300 text-gray-700" + > + 도로자동청소시스템 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 세척살수용수 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 178 + </td> + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 구리 공공하수처리장 재이용수 + </td> + </tr> + </tbody> + </table> + </div> + </section> + + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx new file mode 100644 index 000000000..6f3f72883 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx @@ -0,0 +1,173 @@ +// apps/dm/app/(landing)/reuse/business/sewage/hwanggu/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="수원시 황구지천 하천유지용수" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "수원시 황구지천", href: "/reuse/business/sewage/hwanggu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/hwanggu_01.png", alt: "수원시 황구지천 하수 재이용 1" }, + { src: "/images/landing/business/sewage/hwanggu_02.png", alt: "수원시 황구지천 하수 재이용 2" }, + { src: "/images/landing/business/sewage/hwanggu_03.png", alt: "수원시 황구지천 하수 재이용 3" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 수원시 황수원시 황구지천 하수처리수 재이용사업 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업목적 */} + <section className="mt-6"> + <SectionTitle>사업목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로 주민들에게 친수 공간 제공 및 하천 생태계 보전 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : 110억원<br/> + 위 치 : 수원시 권선구 호매실천, 금곡천<br/> + 관 로 : 3.9km<br/> + 공 급 량 : 25,000㎥/일(하수처리시설 용량: 45,000㎥/일)<br/> + </p> + </section> + + <Hr className="my-11" /> + + {/* 공급계획 */} + <section className="pb-[200px]"> + <SectionTitle>공급계획</SectionTitle> + {/* 데이터 테이블 */} + <div className="overflow-x-auto mt-8"> + <table className="w-full border border-[#E3E7EA] text-center"> + <thead> + <tr> + {/* 구분 */} + <th className="w-1/4 bg-[#E3E7EA] text-black font-semibold py-4 border border-gray-300"> + 구분 + </th> + + {/* 공급량 */} + <th className="w-2/4 bg-[#1FA3C6] text-white font-semibold py-4 border border-gray-300"> + 공급량 + </th> + + {/* 비고 */} + <th className="w-2/4 bg-[#72B84F] text-white font-semibold py-4 border border-gray-300"> + 비고 + </th> + </tr> + </thead> + + <tbody> + <tr> + {/* 좌측 병합 */} + <td + rowSpan={3} + className="bg-white font-medium text-gray-700 border border-gray-300" + > + 하천 등 유지용수 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 12,500m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 호매실천 공급 + </td> + </tr> + + <tr> + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 6,250m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 금곡천1 공급 + </td> + </tr> + + <tr> + <td className="bg-white py-6 border border-gray-300 text-gray-700 text-lg font-medium"> + 6,250m³/일 + </td> + + <td className="bg-white py-6 border border-gray-300 text-gray-700"> + 금곡천2 공급 + </td> + </tr> + </tbody> + </table> + </div> + </section> + + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx new file mode 100644 index 000000000..c3bfa2934 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx @@ -0,0 +1,112 @@ +// apps/dm/app/(landing)/reuse/business/sewage/paju/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="경기도 파주시 하수 재이용" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "파주시", href: "/reuse/business/sewage/paju" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/paju_01.png", alt: "파주시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 경기도 파주시 하수재이용<br/> + : 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급<br/> + *LG디스플레이(P10공장), 희성전자, 엔비텍 등 + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 배경 및 목적 */} + <section className="mt-6"> + <SectionTitle>사업 배경 및 목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스) + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="mt-6"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 처리공정 : 전처리 UF + 후처리 RO<br/> + 공급량 : 4만m³/일<br/> + 관로 : 13.6km<br/> + 사업비 : 651억원<br/> + 위치 : 경기도 파주시 금촌동 714번지 일원<br/> + 시설용량 : 41,20m³/일 + </p> + </section> + + <Hr className="my-11" /> + + {/* 특이사항 */} + <section className="pb-[200px]"> + <SectionTitle>특이사항</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례 + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx b/apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx new file mode 100644 index 000000000..cd77110a8 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx @@ -0,0 +1,101 @@ +// apps/dm/app/(landing)/reuse/business/sewage/yeosu/page.tsx +"use client"; + +import SubPageHeader from "@/components/SubPageHeader"; +import CaseImageSlider from "@/components/CaseImageSlider"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-2xl font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +// type ImgCardProps = { +// src: string; +// alt: string; +// }; + +// function ImgCard({ src, alt }: ImgCardProps) { +// return ( +// <div className="w-full overflow-x-auto"> +// <img +// src={src} +// alt={alt} +// className="block mx-auto max-w-none" +// /> +// </div> +// ); +// } + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="여수시 하수처리수 재이용 민간투자사업(BTO)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 사례" }, + { label: "하수 재이용 사례" }, + { label: "여수시", href: "/reuse/business/sewage/yeosu" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 슬라이드 이미지 영역 */} + <CaseImageSlider + className="mt-8" + maxWidthClassName="max-w-[760px]" + slides={[ + { src: "/images/landing/business/sewage/yeosu_01.png", alt: "여수시 하수 재이용 1" }, + ]} + /> + + {/* 사업명 */} + <section className="mt-6"> + <SectionTitle>사업명</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 여수시 하수처리수 재이용 민간 투자사업(BTO) + </p> + </section> + + <Hr className="my-11" /> + + {/* 사업 목적 */} + <section className="mt-6"> + <SectionTitle>사업 목적</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 수요자가 원하는 양질의 공업용수를 저렴하게 곰급하여 한정된 수자원의 효율적 이용 및 지역적 물 부족 해소, 하수재이용을 통한 오염총량 저감 및 방류수역 수질개선 + </p> + </section> + + {/* 사업 주요 내용 */} + <section className="pb-[200px]"> + <SectionTitle>사업 주요 내용</SectionTitle> + <p className="mt-8 text-base text-gray-600"> + 총사업비 : GS칼텍스, YNCC(주) 등 750억원<br/> + 시행사 : 여수그린허브(주)<br/> + 운영사 : 테크로스<br/> + 위치: 여수시 신월로 2844일원 (여수 하수처리시설 부지 내)<br/> + 처리공법 : 전처리시설(MF) + 주처리시설(RO)<br/> + 시설용량 및 공급량 : 5만m³/일<br/> + 관로 : 18.6km + </p> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/intro/greywater/page.tsx b/apps/dm_new/app/(with-layout)/intro/greywater/page.tsx new file mode 100644 index 000000000..fec020ece --- /dev/null +++ b/apps/dm_new/app/(with-layout)/intro/greywater/page.tsx @@ -0,0 +1,91 @@ +// apps/dm/app/(landing)/reuse/intro/greywater/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png"; +import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png"; +import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="중수도" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "중수도" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 중수 재이용 소개 */} + <section className="mt-6"> + <SectionTitle>중수 재이용 소개</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterIntro} alt="중수 재이용 소개" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 중수도 설치 의무 대상 */} + <section> + <SectionTitle>중수도 설치 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterMandatory} alt="중수도 설치 의무 대상 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 중수도 시설기준 및 관리기준 */} + <section className="pb-[200px]"> + <SectionTitle>중수도 시설기준 및 관리기준</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterStandard} alt="중수도 시설기준 및 관리기준 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/intro/law/page.tsx b/apps/dm_new/app/(with-layout)/intro/law/page.tsx new file mode 100644 index 000000000..0d903a32a --- /dev/null +++ b/apps/dm_new/app/(with-layout)/intro/law/page.tsx @@ -0,0 +1,130 @@ +// apps/dm/app/(landing)/reuse/intro/law/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import lawTimeline from "@/public/images/landing/intro/law_timeline.png"; +import performanceLeft from "@/public/images/landing/intro/performance_left.png"; +import domesticTextbox from "@/public/images/landing/intro/domestic_textbox.png"; +import policyTimeline from "@/public/images/landing/intro/policy_timeline.png"; +import relNote from "@/public/images/landing/intro/rel_note.png"; +import linkage from "@/public/images/landing/intro/linkage.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function WaterReuseLawPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="물 재이용 법" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "물 재이용 법" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 법제도/계획 수립 타임라인 */} + <section className="mt-6"> + <SectionTitle>법제도 · 계획 및 기본계획 수립</SectionTitle> + <div className="mt-8"> + <ImgCard src={lawTimeline} alt="법제도 타임라인" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 제2차 계획 대비 실적 현황 (2컬럼) */} + <section> + <SectionTitle>제 2차 계획 대비 실적 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={performanceLeft} alt="실적 현황 요약" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 국내 관련 현황 */} + <section> + <SectionTitle>국내 관련 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={domesticTextbox} alt="국내 관련 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 정책 변천 추이 */} + <section> + <SectionTitle>물 재이용 정책 변천 추이</SectionTitle> + <div className="mt-8"> + <ImgCard src={policyTimeline} alt="정책 변천 타임라인" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 5) 타 법령과의 관계도 */} + <section> + <SectionTitle>타법령과의 관계도</SectionTitle> + <div className="mt-8"> + <ImgCard src={relNote} alt="관계도 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 6) 국내 연계 현황 */} + <section className="pb-[200px]"> + <SectionTitle>국내 연계 현황</SectionTitle> + <div className="mt-8"> + <div className="mx-[20px] mb-8"> + <p className="text-[24px] font-semibold mb-2">주요 국가계획의 정책방향과 연계한 물 재이용 정책 필요</p> + <p className="text-[#4F4F4F]">국가 상위계획의 정책 방향이 기후위기 대응 등 수자원의 공급 안전성을 위한 물 재이용 활성화로 정책 실효성 제고 필요<br /> + • 물관리 계획(국가수도기본계획, 국가 및 유역하수도종합계획, 하천유역수자원관리계획 등)과의 연계성 확보 → 통합적인 관점에서 물이용 체계 구축 필요 + </p> + </div> + <ImgCard src={linkage} alt="국내 연계 현황" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/intro/layout.tsx b/apps/dm_new/app/(with-layout)/intro/layout.tsx new file mode 100644 index 000000000..e025f4c80 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/intro/layout.tsx @@ -0,0 +1,13 @@ +import type { ReactNode } from "react"; +import Lnb from "@/components/Lnb"; + +export default function IntroLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb /> + <main className="flex-1 px-[100px] pt-[80px]"> + {children} + </main> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/intro/rain/page.tsx b/apps/dm_new/app/(with-layout)/intro/rain/page.tsx new file mode 100644 index 000000000..3f88d7f58 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/intro/rain/page.tsx @@ -0,0 +1,102 @@ +// apps/dm/app/(landing)/reuse/intro/law/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import rainreuseIntro from "@/public/images/landing/intro/rainreuse_intro.png"; +import rainreuseMandatory from "@/public/images/landing/intro/rainreuse_mandatory.png"; +import rainwaterDesign from "@/public/images/landing/intro/rainwater_design.png"; +import rainwaterStandards from "@/public/images/landing/intro/rainwater_standards.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function RainwaterReusePage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="빗물 이용시설" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "빗물 이용시설" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 빗물 재이용 소개 */} + <section className="mt-6"> + <SectionTitle>빗물 재이용 소개</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainreuseIntro} alt="빗물 재이용 소개" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 빗물이용시설 설치 관리 법적 의무 대상 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설 설치 관리 법적 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainreuseMandatory} alt="법적 의무 대상 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 빗물이용시설 설계 방안 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설 설계 방안</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterDesign} alt="빗물이용시설 설계 방안 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 빗물이용시설 시설기준 및 관리기준 */} + <section className="pb-[200px]"> + <SectionTitle>빗물이용시설 시설기준 및 관리기준</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterStandards} alt="빗물이용시설 시설기준 및 관리기준 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/intro/sewage/page.tsx b/apps/dm_new/app/(with-layout)/intro/sewage/page.tsx new file mode 100644 index 000000000..526fd4e9c --- /dev/null +++ b/apps/dm_new/app/(with-layout)/intro/sewage/page.tsx @@ -0,0 +1,80 @@ +// apps/dm/app/(landing)/reuse/intro/treated/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png"; +import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function TreatedWaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="하수처리수" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 소개", href: "/reuse/intro/law" }, + { label: "하수처리수" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 하수처리수 */} + <section className="mt-6"> + <SectionTitle>하수처리수</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedwaterIntro} alt="하수처리수 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 하수처리수 재이용시설 의무 대상 */} + <section className="pb-[200px]"> + <SectionTitle>하수처리수 재이용시설 의무 대상</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedwaterMandatory} alt="하수처리수 재이용시설 의무 대상 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/layout.tsx b/apps/dm_new/app/(with-layout)/layout.tsx new file mode 100644 index 000000000..2c31919bc --- /dev/null +++ b/apps/dm_new/app/(with-layout)/layout.tsx @@ -0,0 +1,19 @@ +// app/(with-layout)/layout.tsx +/** + * 메인을 제외한 메뉴안 페이지의 layout + */ + +import "../reuse.module.css"; +import { StoreProvider } from "../store"; +import TopNav from "@/components/TopNav"; + +export default function WithLayout({ children }: { children: React.ReactNode }) { + return ( + <StoreProvider> + <div className="pt-[83px]">{/* TopNav 높이만큼 */} + <TopNav /> + <main className="ml-[260px]">{children}</main>{/* Lnb 너비만큼 */} + </div> + </StoreProvider> + ); +} diff --git a/apps/dm_new/app/(with-layout)/status/greywater/page.tsx b/apps/dm_new/app/(with-layout)/status/greywater/page.tsx new file mode 100644 index 000000000..b84a6760d --- /dev/null +++ b/apps/dm_new/app/(with-layout)/status/greywater/page.tsx @@ -0,0 +1,102 @@ +// apps/dm/app/(landing)/reuse/cases/greywater/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import greywaterOverall from "@/public/images/landing/cases/greywater_overall.png"; +import greywaterFactory from "@/public/images/landing/cases/greywater_factory.png"; +import greywaterBuilding from "@/public/images/landing/cases/greywater_building.png"; +import greywaterUsage from "@/public/images/landing/cases/greywater_usage.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="중수도(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "중수도(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외) */} + <section className="mt-6"> + <SectionTitle>전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterOverall} alt="전체 중수도 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 중수공장 및 발전시설 내 중수도 설치 및 이용 현황 */} + <section> + <SectionTitle>공장 및 발전시설 내 중수도 설치 및 이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterFactory} alt="공장 및 발전시설 내 중수도 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 중수도 건축물별 이용량 현황 */} + <section className="pb-16"> + <SectionTitle>중수도 건축물별 이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterBuilding} alt="중수도 건축물별 이용량 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 중수도 용도별 이용량 현황 */} + <section className="pb-[200px]"> + <SectionTitle>중수도 용도별 이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={greywaterUsage} alt="중수도 용도별 이용량 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/status/layout.tsx b/apps/dm_new/app/(with-layout)/status/layout.tsx new file mode 100644 index 000000000..614eb47ed --- /dev/null +++ b/apps/dm_new/app/(with-layout)/status/layout.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; +import Lnb from "@/components/Lnb"; + +export default function CasesLayout({ children }: { children: ReactNode }) { + return ( + <div className="flex gap-0"> + <Lnb /> + <main className="flex-1 px-[100px] pt-[80px]">{children}</main> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/status/rain/page.tsx b/apps/dm_new/app/(with-layout)/status/rain/page.tsx new file mode 100644 index 000000000..aa33147f0 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/status/rain/page.tsx @@ -0,0 +1,91 @@ +// apps/dm/app/(landing)/reuse/cases/rain/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import rainwaterBuilding from "@/public/images/landing/cases/rainwater_building.png"; +import rainwaterUsage from "@/public/images/landing/cases/rainwater_usage.png"; +import rainwaterUsageAmount from "@/public/images/landing/cases/rainwater_usage_amount.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="빗물이용시설(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "빗물이용시설(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 빗물이용시설의 건축물별 빗물이용량 현황 */} + <section className="mt-6"> + <SectionTitle>빗물이용시설의 건축물별 빗물이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterBuilding} alt="빗물이용시설의 건축물별 빗물이용량 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 빗물이용시설의 용도별 분포 (2023년 기준) */} + <section> + <SectionTitle>빗물이용시설의 용도별 분포 (2023년 기준)</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterUsage} alt="빗물이용시설의 용도별 분포 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 빗물이용시설의 용도별 빗물이용량 현황 */} + <section className="pb-[200px]"> + <SectionTitle>빗물이용시설의 용도별 빗물이용량 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={rainwaterUsageAmount} alt="빗물이용시설의 용도별 빗물이용량 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/(with-layout)/status/sewage/page.tsx b/apps/dm_new/app/(with-layout)/status/sewage/page.tsx new file mode 100644 index 000000000..19214da02 --- /dev/null +++ b/apps/dm_new/app/(with-layout)/status/sewage/page.tsx @@ -0,0 +1,102 @@ +// apps/dm/app/(landing)/reuse/cases/treated/page.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import SubPageHeader from "@/components/SubPageHeader"; +import treatedWaterYear from "@/public/images/landing/cases/treated_water_year.png"; +import treatedWaterUsage from "@/public/images/landing/cases/treated_water_usage.png"; +import treatedWaterFacility from "@/public/images/landing/cases/treated_water_facility.png"; +import treatedWaterRegion from "@/public/images/landing/cases/treated_water_region.png"; + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( + <div className="flex items-center gap-3"> + <span className="h-5 w-[3px] bg-sky-500" /> + <h3 className="text-[24px] font-bold text-neutral-900">{children}</h3> + </div> + ); +} + +type HrProps = { + className?: string; +}; + +function Hr({ className = "" }: HrProps) { + return <div className={`h-[2px] w-full bg-black/10 ${className}`} />; +} + +type ImgCardProps = { + src: StaticImageData; + alt: string; +}; + +function ImgCard({ src, alt }: ImgCardProps) { + return ( + <div className="w-full overflow-x-auto"> + <Image + src={src} + alt={alt} + className="block mx-auto max-w-none" + style={{ width: "auto", height: "auto" }} + /> + </div> + ); +} + +export default function GreywaterPage() { + return ( + <div className="bg-white"> + <SubPageHeader + title="하수처리수 재이용시설(현황)" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "물 재이용 현황", href: "/reuse/cases/rain" }, + { label: "하수처리수 재이용시설(현황)" }, + ]} + hideFooterLine + hideGuideText + wrapClassName="!pb-0" + /> + + <Hr className="my-7" /> + + {/* 1) 연도별 하수처리수 재이용 현황 */} + <section className="mt-6"> + <SectionTitle>연도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterYear} alt="연도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 2) 용도별 하수처리수 재이용 현황 */} + <section> + <SectionTitle>용도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterUsage} alt="용도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 3) 하수처리시설 설치 및 이용 현황 */} + <section> + <SectionTitle>하수처리시설 설치 및 이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterFacility} alt="하수처리시설 설치 및 이용 현황 설명" /> + </div> + </section> + + <Hr className="my-11" /> + + {/* 4) 시도별 하수처리수 재이용 현황 */} + <section className="pb-[200px]"> + <SectionTitle>시도별 하수처리수 재이용 현황</SectionTitle> + <div className="mt-8"> + <ImgCard src={treatedWaterRegion} alt="시도별 하수처리수 재이용 현황 설명" /> + </div> + </section> + </div> + ); +} diff --git a/apps/dm_new/app/api/postcode/route.ts b/apps/dm_new/app/api/postcode/route.ts new file mode 100644 index 000000000..e00399234 --- /dev/null +++ b/apps/dm_new/app/api/postcode/route.ts @@ -0,0 +1,60 @@ +import { NextResponse } from "next/server"; + +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const query = (searchParams.get("query") ?? "").trim(); + const page = Number(searchParams.get("page") ?? "1"); + + if (!query) { + return NextResponse.json({ totalCount: 0, items: [] }); + } + + const confmKey = process.env.JUSO_API_KEY; + + // ✅ 키 없으면 UI 개발용 더미 + if (!confmKey) { + return NextResponse.json({ + totalCount: 3, + items: [ + { + zipNo: "06236", + roadAddr: "서울특별시 강남구 테헤란로 152, 강남파이낸스센터 12층", + jibunAddr: "서울특별시 강남구 역삼동 737-10 강남파이낸스센터", + }, + { + zipNo: "06180", + roadAddr: "서울특별시 강남구 테헤란로 231, 센터필드 WEST동 3층", + jibunAddr: "서울특별시 강남구 역삼동 824-11 센터필드 WEST동", + }, + { + zipNo: "06142", + roadAddr: "서울특별시 강남구 테헤란로 521, 삼성동 코엑스인터컨티넨탈호텔 별관", + jibunAddr: "서울특별시 강남구 삼성동 159-1 코엑스인터컨티넨탈호텔", + }, + ], + }); + } + + // ✅ 행안부 주소검색 API(JSON) + const url = new URL("https://business.juso.go.kr/addrlink/addrLinkApi.do"); + url.searchParams.set("confmKey", confmKey); + url.searchParams.set("currentPage", String(page)); + url.searchParams.set("countPerPage", "20"); + url.searchParams.set("keyword", query); + url.searchParams.set("resultType", "json"); + + const r = await fetch(url.toString(), { cache: "no-store" }); + const j = await r.json(); + + const common = j?.results?.common; + const totalCount = Number(common?.totalCount ?? 0); + const list = j?.results?.juso ?? []; + + const items = list.map((x: any) => ({ + zipNo: x.zipNo, + roadAddr: x.roadAddr, + jibunAddr: x.jibunAddr, + })); + + return NextResponse.json({ totalCount, items }); +} \ No newline at end of file diff --git a/apps/dm_new/app/common/comFunction.ts b/apps/dm_new/app/common/comFunction.ts new file mode 100644 index 000000000..ec4894d49 --- /dev/null +++ b/apps/dm_new/app/common/comFunction.ts @@ -0,0 +1,65 @@ +import type { RegionData, } from "../store/wrStatus/type"; + + + +export const regionData:RegionData[] = [ + { name: '서울', x: 170, y: 140, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '인천', x: 120, y: 150, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '경기', x: 210, y: 210, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '강원', x: 330, y: 120, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '충북', x: 260, y: 280, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '충남', x: 100, y: 320, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '세종', x: 180, y: 305, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '대전', x: 195, y: 360, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '전북', x: 170, y: 450, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '전남', x: 160, y: 600, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '광주', x: 120, y: 530, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '경북', x: 400, y: 320, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '경남', x: 310, y: 530, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '대구', x: 350, y: 450, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '울산', x: 450, y: 500, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '부산', x: 420, y: 550, values: [0.3, 0.3, 0.2, 0.2] }, + { name: '제주', x: 60, y: 750, values: [0.3, 0.3, 0.2, 0.2] }, + ] + +export const regionIdMap: Record<string, string> = { + 서울: '서울특별시', + 부산: '부산광역시', + 대구: '대구광역시', + 인천: '인천광역시', + 광주: '광주광역시', + 대전: '대전광역시', + 울산: '울산광역시', + 세종: '세종특별자치시', + 경기: '경기도', + 강원: '강원특별자치도', + 충북: '충청북도', + 충남: '충청남도', + 전북: '전라북도', + 전남: '전라남도', + 경북: '경상북도', + 경남: '경상남도', + 제주: '제주특별자치도' + }; + + +export const makeRegion = (region: string) => { + if (region === '서울특별시') return '서울' + if (region === '부산광역시') return '부산' + if (region === '대구광역시') return '대구' + if (region === '인천광역시') return '인천' + if (region === '광주광역시') return '광주' + if (region === '대전광역시') return '대전' + if (region === '울산광역시') return '울산' + if (region === '세종특별자치시') return '세종' + if (region === '경기도') return '경기' + if (region === '강원특별자치도') return '강원' + if (region === '충청북도') return '충북' + if (region === '충청남도') return '충남' + if (region === '전북특별자치도') return '전북' + if (region === '전라남도') return '전남' + if (region === '경상북도') return '경북' + if (region === '경상남도') return '경남' + if (region === '제주특별자치도') return '제주' + return region; +} diff --git a/apps/dm_new/app/common/transValueFormat.tsx b/apps/dm_new/app/common/transValueFormat.tsx new file mode 100644 index 000000000..4d76d84e0 --- /dev/null +++ b/apps/dm_new/app/common/transValueFormat.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +type CommonProps = { + value: string | number | ''; + format: string; +} + +export default function TransValueFormat({value='', format=''}: CommonProps) { + return ( + format === 'yyyyMMdd' ? + <>{value.toString().substring(0,4)+'-'+value.toString().substring(4,6)+'-'+value.toString().substring(6,8)}</> + : format === 'number' ? + <>{String(value ?? '').includes('.') + ? String(value).split('.').map((part, index) => + index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part + ).join('.') + : String(value ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</> + : format === 'float1' ? + <>{String(value ?? '').includes('.') + ? String(value).split('.').map((part, index) => + index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part + ).join('.') + : String(value ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</> + : <>{value}</> + ) +} \ No newline at end of file diff --git a/apps/dm_new/app/globals.css b/apps/dm_new/app/globals.css new file mode 100644 index 000000000..2fee793a1 --- /dev/null +++ b/apps/dm_new/app/globals.css @@ -0,0 +1,83 @@ +/* @import "../../../packages/common/components/src/base.css"; */ +/* @import "../../../packages/common/components/src/base.css"; */ + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55% + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +@font-face { + font-family: "NotoSansKR"; + src: url("/fonts/NotoSansKR-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +body { + font-family: "NotoSansKR", sans-serif; +} diff --git a/apps/dm_new/app/layout.tsx b/apps/dm_new/app/layout.tsx new file mode 100644 index 000000000..0e45865e7 --- /dev/null +++ b/apps/dm_new/app/layout.tsx @@ -0,0 +1,25 @@ +"use client"; +import "./globals.css"; +import { setCookie } from "../src/shared/lib"; + +setCookie("site-id-key", process.env.NEXT_PUBLIC_SITE_ID || "DM"); + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + + return ( + <html lang="ko"> + <head> + <title>물재이용 대민 + + +
    + {children} +
    + + + ); +} diff --git a/apps/dm_new/app/main/SideIndicator.tsx b/apps/dm_new/app/main/SideIndicator.tsx new file mode 100644 index 000000000..8f4cea164 --- /dev/null +++ b/apps/dm_new/app/main/SideIndicator.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import styles from "../reuse.module.css"; + +export interface SideIndicatorItem { + id: string; + label: string; // 상단/공용 라벨 + sideLabel?: string; // 사이드 인디게이터 전용 라벨(옵션) +} + +export default function SideIndicator({ + items, + activeId, +}: { + items: SideIndicatorItem[]; + activeId: string; +}) { + const [manualActiveId, setManualActiveId] = useState(null); + const clearTimerRef = useRef(null); + + const currentId = (manualActiveId ?? activeId ?? "main") || "main"; + + // ✅ manualActiveId가 observer(activeId)와 불일치하기 시작하면 즉시 해제 (stuck 방지) + useEffect(() => { + if (!manualActiveId) return; + if (activeId && activeId !== manualActiveId) setManualActiveId(null); + }, [activeId, manualActiveId]); + + // ✅ fallback: 스무스 스크롤 시간이 길어도 manual이 오래 남지 않게 타임아웃 해제 + useEffect(() => { + return () => { + if (clearTimerRef.current) window.clearTimeout(clearTimerRef.current); + }; + }, []); + + const scrollTo = (id: string) => { + setManualActiveId(id); + + // 기존 타이머 제거 후 새로 설정 + if (clearTimerRef.current) window.clearTimeout(clearTimerRef.current); + clearTimerRef.current = window.setTimeout(() => { + setManualActiveId(null); + }, 1600); + + if (id === "main") { + window.scrollTo({ top: 0, behavior: "smooth" }); + return; + } + + document.getElementById(id)?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }; + + return ( + + ); +} diff --git a/apps/dm_new/app/main/_hooks/useScrollSpy.ts b/apps/dm_new/app/main/_hooks/useScrollSpy.ts new file mode 100644 index 000000000..8274f277d --- /dev/null +++ b/apps/dm_new/app/main/_hooks/useScrollSpy.ts @@ -0,0 +1,63 @@ +"use client"; +import { useEffect, useRef, useState } from "react"; + +const DEFAULT_THRESHOLD = [0, 0.01, 0.1, 0.25, 0.5, 0.75]; + +export function useScrollSpy(sectionIds: string[], options?: IntersectionObserverInit) { + const [activeId, setActiveId] = useState(sectionIds[0] ?? ""); + const ratiosRef = useRef>(new Map()); + const activeIdRef = useRef(activeId); + + useEffect(() => { + activeIdRef.current = activeId; + }, [activeId]); + + const threshold = options?.threshold ?? DEFAULT_THRESHOLD; + const thresholdKey = Array.isArray(threshold) ? threshold.join("|") : String(threshold); + + useEffect(() => { + const els = sectionIds + .map((id) => document.getElementById(id)) + .filter(Boolean) as HTMLElement[]; + + if (!els.length) return; + + const DEFAULT_ROOT_MARGIN = "83px 0px -65% 0px"; + + // 초기값 세팅(안정화) + ratiosRef.current.clear(); + els.forEach((el) => ratiosRef.current.set(el.id, 0)); + + const io = new IntersectionObserver( + (entries) => { + for (const e of entries) { + const id = (e.target as HTMLElement).id; + ratiosRef.current.set(id, e.isIntersecting ? e.intersectionRatio : 0); + } + + // ✅ 전체 섹션 중 가장 ratio 큰 항목을 active로 + let bestId = activeIdRef.current; + let bestRatio = -1; + + for (const [id, ratio] of ratiosRef.current.entries()) { + if (ratio > bestRatio) { + bestRatio = ratio; + bestId = id; + } + } + + if (bestId) setActiveId(bestId); + }, + { + root: options?.root ?? null, + threshold, + rootMargin: options?.rootMargin ?? DEFAULT_ROOT_MARGIN, + } + ); + + els.forEach((el) => io.observe(el)); + return () => io.disconnect(); + }, [sectionIds, options?.root, options?.rootMargin, threshold, thresholdKey]); + + return activeId; +} diff --git a/apps/dm_new/app/main/_sections/Cases.tsx b/apps/dm_new/app/main/_sections/Cases.tsx new file mode 100644 index 000000000..757950369 --- /dev/null +++ b/apps/dm_new/app/main/_sections/Cases.tsx @@ -0,0 +1,69 @@ +// apps/dm/app/(landing)/reuse/_sections/Cases.tsx +"use client"; + +import Image from "next/image"; +import section03 from "@/public/images/landing/section_03.png"; + +export default function Cases() { + return ( +
    +
    +
    + + {/* 좌측 : 카피 영역 */} +
    +
    +

    + 빗물+중수도+하수도 +

    + +

    + + 데이터로 보는 + + + 물 재이용 현황 + +

    + + +
    +
    + + {/* 우측 : 이미지 카드 */} +
    +
    + 물 재이용 현황 데이터 테이블 +
    +
    + +
    +
    +
    + ); +} diff --git a/apps/dm_new/app/main/_sections/Hero.tsx b/apps/dm_new/app/main/_sections/Hero.tsx new file mode 100644 index 000000000..fa3c525de --- /dev/null +++ b/apps/dm_new/app/main/_sections/Hero.tsx @@ -0,0 +1,36 @@ +// app/main/_sections/Hero.tsx +import Image from "next/image"; +import styles from "../../reuse.module.css"; + +export default function Hero() { + return ( +
    +
    +
    + +
    +
    +

    물 재이용

    +

    + 빗물, 오수, 하수처리수, 폐수처리수, 발전소 온배수를 재이용처리하여
    + 생활, 공업, 농업, 조경, 하천유지 등의 용도로 재이용합니다. +

    +

    + 전국 하수 재이용률 15.6%(2024년 통계 기준),
    + 물재이용 활성화를 위한 재이용 수요신청, 매칭을 추진하고 있습니다. +

    +
    +
    +
    + + scroll down +
    +
    + ); +} diff --git a/apps/dm_new/app/main/_sections/IntroCards.tsx b/apps/dm_new/app/main/_sections/IntroCards.tsx new file mode 100644 index 000000000..c8c69ad7b --- /dev/null +++ b/apps/dm_new/app/main/_sections/IntroCards.tsx @@ -0,0 +1,93 @@ +// apps/dm/app/(landing)/reuse/_sections/IntroCards.tsx +"use client"; + +import Image, { type StaticImageData } from "next/image"; +import Link from "next/link"; +import card01 from "@/public/images/landing/card_01.jpg"; +import card02 from "@/public/images/landing/card_02.jpg"; +import card03 from "@/public/images/landing/card_03.jpg"; +import card04 from "@/public/images/landing/card_04.jpg"; + +type IntroCardItem = { + title: string; + desc: string; + image: StaticImageData; + href: string; +}; + +const INTRO_CARDS: IntroCardItem[] = [ + { + title: "물 재이용 법", + desc: "물의 재이용 촉진 및 지원에 관한 법률을 시행하고, 일부 법률은 개정되었습니다.", + image: card01, + href: "/reuse/intro/law", + }, + { + title: "친환경 수자원 빗물", + desc: "가뭄을 극복하고 홍수를 예방하기 위해 비가 내릴 때 빗물을 모아두었다가 사용합니다.", + image: card02, + href: "/reuse/intro/rain", + }, + { + title: "알뜰하게 재이용되는 중수", + desc: "한번 사용한 물을 중수도 시설을 통해 재생하여 개별적 또는 지역적으로 재이용할 수 있습니다.", + image: card03, + href: "/reuse/intro/greywater", + }, + { + title: "하수처리수 재이용", + desc: "사용하고 버려진 공공하수처리장으로 모여진 물을 용도에 맞는 처리 후 재이용 할 수 있습니다.", + image: card04, + href: "/reuse/intro/treated", + }, +]; + +export default function IntroCards() { + return ( +
    +
    +
    +
    +

    물 재이용 소개

    +
    +
    +

    + 지속 가능한 미래를 위한 약속, 깨끗한 물의 새로운 순환을 시작합니다. +

    +
    + +
    +
    + {INTRO_CARDS.map((card, idx) => ( + + {/* 배경 이미지 (이미 그라디언트 포함) */} + {card.title} + + {/* 텍스트 영역 */} +
    +

    + {card.title} +

    +

    + {card.desc} +

    +
    + + ))} +
    +
    +
    +
    + ); +} diff --git a/apps/dm_new/app/main/_sections/ProcessFlow.tsx b/apps/dm_new/app/main/_sections/ProcessFlow.tsx new file mode 100644 index 000000000..6caea2ee5 --- /dev/null +++ b/apps/dm_new/app/main/_sections/ProcessFlow.tsx @@ -0,0 +1,22 @@ +// apps/dm/app/(landing)/reuse/_sections/ProcessFlow.tsx +import CaseTabsCarousel from "@/components/CaseTabsCarousel"; + + +export default function ProcessFlow() { + return ( +
    +
    +
    +
    +

    물 재이용 사례

    +
    +
    +
    + + {/* 탭메뉴 영역 */} + + +
    +
    + ); +} \ No newline at end of file diff --git a/apps/dm_new/app/main/layout.tsx b/apps/dm_new/app/main/layout.tsx new file mode 100644 index 000000000..b1304a66a --- /dev/null +++ b/apps/dm_new/app/main/layout.tsx @@ -0,0 +1,13 @@ +// app/main/layout.tsx +import "../reuse.module.css"; +import TopNav from "@/components/TopNav"; +import { StoreProvider } from "../store"; + +export default function ReuseLandingLayout({ children }: { children: React.ReactNode }) { + return ( + + +
    {children}
    {/* TopNav 높이만큼 */} +
    + ); +} diff --git a/apps/dm_new/app/main/page.tsx b/apps/dm_new/app/main/page.tsx new file mode 100644 index 000000000..2ab2b3c7f --- /dev/null +++ b/apps/dm_new/app/main/page.tsx @@ -0,0 +1,60 @@ +// apps/dm/app/(landing)/reuse/page.tsx +"use client"; + +import { useMemo } from "react"; +import TopNav from "@/components/TopNav"; +import SideIndicator from "./SideIndicator"; +import { useScrollSpy } from "./_hooks/useScrollSpy"; + +import Hero from "./_sections/Hero"; +import IntroCards from "./_sections/IntroCards"; +import Cases from "./_sections/Cases"; +import ProcessFlow from "./_sections/ProcessFlow"; + +const LANDING_SECTIONS = [ + { id: "main", label: "Main" }, + { + id: "intro", + label: "물 재이용 소개", // ✅ 상단 메뉴용 + sideLabel: "소개", // ✅ 사이드 인디게이터용 + }, + { + id: "cases", + label: "물 재이용 현황", // ✅ 상단 메뉴용 + sideLabel: "현황", // ✅ 사이드 인디게이터용 + }, + { + id: "processflow", + label: "물 재이용 사례", // ✅ 상단 메뉴용 + sideLabel: "재이용사례", // ✅ 사이드 인디게이터용 + }, +]; + +export default function ReuseLandingPage() { + const sectionIds = useMemo(() => LANDING_SECTIONS.map((s) => s.id), []); + const activeId = useScrollSpy(sectionIds); + + return ( +
    + + {/* 헤더 */} + + + {/* 사이드 인디게이터 */} + + + {/* 컨텐츠 영역 */} +
    + {/* 물 재이용 */} + + {/* 재이용 소개 */} + + {/* 재이용 사례 */} + + {/* 물 재이용 사업 */} + +
    + +
    + ); +} diff --git a/apps/dm_new/app/reuse.module.css b/apps/dm_new/app/reuse.module.css new file mode 100644 index 000000000..31631d7ba --- /dev/null +++ b/apps/dm_new/app/reuse.module.css @@ -0,0 +1,238 @@ +/* apps/dm/app/(landing)/reuse/reuse.module.css */ + +.hero { + position: relative; + min-height: 100vh; + overflow: hidden; + font-family: "NotoSansKR", sans-serif; +} + +.heroBg { + position: absolute; + inset: 0; + background: url("/images/landing/hero_bg.jpg") center/cover no-repeat; + transform: scale(1.02); +} + +.heroOverlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.50); +} + +/* ===== Side Indicator (첨부 이미지 스타일) ===== */ +.sideIndicator { + position: fixed; + right: 28px; + top: 50%; + transform: translateY(-50%); + z-index: 40; + + /* 원형 배지 폭만큼만 */ + width: 60px; + + /* (디자인처럼) 스크롤 영역 위에 뜨되 클릭 가능 */ + pointer-events: none; +} + +/* 세로 라인: 연한 하늘색 + 점선(도트) 느낌 */ +.sideIndicatorLine { + position: absolute; + left: 50%; + top: -150px; + bottom: -150px; + width: 2px; + transform: translateX(-50%); + background: rgba(46, 177, 235, 0.25); + background-size: 2px 16px; + background-repeat: repeat-y; + z-index: -1; +} + +/* 리스트는 가운데 정렬로 수직 배치 */ +.sideIndicatorList { + list-style: none; + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + align-items: center; + + gap: 56px; /* 4개 간격(시안에 맞게 조절) */ + pointer-events: auto; +} + +.sideIndicatorItem { + display: flex; + justify-content: center; +} + +/* 공통 버튼 */ +.indicatorBtn { + border: 0; + background: transparent; + padding: 0; + cursor: pointer; + position: relative; + z-index: 2; + display: grid; + place-items: center; +} + +/* ✅ 기본: 작은 점 */ +.indicatorDot { + width: 8px; + height: 8px; + border-radius: 999px; + background: rgba(30, 167, 225, 0.55); + border: 1px solid rgba(30, 167, 225, 0.7); +} + +/* ✅ 활성: 커지는 큰 원(텍스트 포함) */ +.indicatorActive { + width: 83px; + height: 83px; + border-radius: 999px; + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +/* Main 활성: 흰 배경 + 검정 텍스트 */ +.indicatorMainActive { + background: #ffffff; + border: 2px solid rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.75); +} + +/* 섹션 활성: 파란 배경 + 흰 텍스트 */ +.indicatorSectionActive { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #ffffff; + /* box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); */ +} + +/* 활성 텍스트 */ +.indicatorText { + font-size: 12px; + font-weight: 700; + line-height: 1.1; + text-align: center; + padding: 0 8px; + word-break: keep-all; + white-space: normal; +} + +/* 공통 원형 버튼 */ +.sideIndicatorDot { + width: 58px; + height: 58px; + border-radius: 999px; + + display: grid; + place-items: center; + + cursor: pointer; + user-select: none; + + transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, + border-color 0.15s ease; + + /* 라인 위로 올라오게 */ + position: relative; + z-index: 1; + + pointer-events: auto; +} + +.sideIndicatorDotText { + font-size: 12px; + font-weight: 600; + line-height: 1.1; + text-align: center; + padding: 0 8px; + + /* 긴 텍스트도 배지 안에서 줄바꿈 가능 */ + word-break: keep-all; + white-space: normal; +} + +/* Main: 흰색 원형 + 회색 텍스트 */ +.sideIndicatorDotMain { + background: #ffffff; + border: 2px solid rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.65); +} + +/* 섹션: 파란 원형 + 흰색 텍스트 */ +.sideIndicatorDotSection { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #ffffff; +} + +/* 활성 섹션: 더 진한 파랑 + 살짝 글로우 */ +.sideIndicatorDotActive { + transform: scale(1.02); + box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); +} + +/* hover 효과(이미지 느낌으로 아주 약하게만) */ +.sideIndicatorDot:hover { + transform: translateY(-1px); +} + +/* 반응형: 모바일에서는 숨김(원하시면 제거) */ +@media (max-width: 768px) { + .sideIndicator { + display: none; + } +} + +/* 중앙 배지 */ +.sideIndicatorBadge { + width: 58px; + height: 58px; + border-radius: 999px; + display: grid; + place-items: center; + font-size: 12px; + font-weight: 600; + text-align: center; + z-index: 1; +} + +/* Main 배지 */ +.badgeMain { + background: #fff; + border: 2px solid rgba(0,0,0,.15); + color: rgba(0,0,0,.65); +} + +/* Section 배지 */ +.badgeSection { + background: #1ea7e1; + border: 2px solid rgba(30, 167, 225, 0.85); + color: #fff; + box-shadow: 0 0 0 8px rgba(30, 167, 225, 0.18); +} + +/* 작은 점 */ +.sideIndicatorDotSmall { + width: 7px; + height: 7px; + border-radius: 999px; + background: rgba(30,167,225,.6); +} + +/* 점 그룹 */ +.sideIndicatorDotsTop, +.sideIndicatorDotsBottom { + display: flex; + flex-direction: column; + align-items: center; + gap: 18px; +} + +.sideIndicatorDotsTop { margin-bottom: 18px; } +.sideIndicatorDotsBottom { margin-top: 22px; } \ No newline at end of file diff --git a/apps/dm_new/app/store/common/index.tsx b/apps/dm_new/app/store/common/index.tsx new file mode 100644 index 000000000..e875c2eca --- /dev/null +++ b/apps/dm_new/app/store/common/index.tsx @@ -0,0 +1,502 @@ +/**==================================== + * 보편적으로 쓰는 store 공간 + ====================================**/ + +import { fetchType, getFetch } from '@/components/ui/api'; + +/** + * STORE - TYPE + */ + +// selectBox +export type SelectDataType = { + text: string; + val: string; +} + +export interface MenuItem { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: MenuItem[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; +} + +// grid header +export type TableUpperProps = { + id: string; + title: string; + upName?: string; + upSequnce?: number; + align? : string; + row? : number; + rowSpan?: number; + colSpan?: number; +} + +// 시도 데이터 +export type sidoDataType = { + adstDstrCd: number, + sido: string, +} + +// 시군구 데이터 +export type sigunDataType = { + adstDstrCd: number, + gugun: string +} + +// 권역 데이터 +export type regionDataType = { + rvbsnCd: string, + rvbsn: string, +} + +// 단위유역 데이터 +export type unitWaterShedDataType = { + rvbsnCd: string, + lgbsn: string, +} + +// 중권역 데이터 +export type middleRegionDataType = { + rvbsnCd: string, + mdmbsn: string, +} + +// 환경청 데이터 +export type envPartDataType = { + lclgvrnmntCd: string, + lclgvrnmntNm: string, + adstDliStrCd: string, + adstDstrCd: string, +} + +export type envPartSelectDataType = SelectDataType & { + adstDliStrCd: string, +} + +/** + * STORE - DATA + */ + +// 시도 데이터 +let selectSidoData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +// 시군구 데이터 +let selectSigunData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +// 권역 데이터 +const selectRegionData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +// 단위유역 데이터 +const selectUnitWaterShedData: SelectDataType[] = [ +]; + +// 환경청 데이터 +let selectEnvPartData: envPartSelectDataType[] = [ + { text: "전체", val: '0', adstDliStrCd: '' } +]; + +// 환경청 내 시도 데이터 +const selectEnvPartSidoData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +// 환경청 내 시군구 데이터 +const selectEnvPartSigunData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +// 구분 데이터 +// const selectPartData:SelectDataType[] = [ +// { text: "행정별", val: '0' }, +// { text: "유역별", val: '01' }, +// { text: "환경청별", val: '02' }, +// ]; + +// 상향 하향 데이터 +const selectUpdownData:SelectDataType[] = [ + { text: "전체", val: '0' }, + { text: "500이하", val: '499' }, + { text: "500이상", val: '500' }, +]; + +// 운영 상태 데이터 +const selectOperationData:SelectDataType[] = [ + { text: "전체", val: '0' }, + { text: "가동중", val: '1' }, + { text: "재가동", val: '2' }, + { text: "가동중지", val: '3' }, + { text: "시설폐쇄", val: '4' }, +]; + +// 시설 구분 데이터 +const selectFacilityPartData:SelectDataType[] = [ + { text: "전체", val: '0' }, + { text: "공공하수처리시설", val: 'SW' }, + { text: "오수처리장", val: 'SS' }, + { text: "개인하수처리시설", val: 'PB' }, + { text: "분뇨처리장", val: 'NS' }, + { text: "폐수처리장", val: 'LW' }, + { text: "침출수처리장", val: 'LC' }, + { text: "기타기초시설", val: 'ET' }, +]; + +// 년도 데이터 +const getYearData = () => { + const year = new Date().getFullYear(); + const year1 = year - 1; + const year2 = year - 2; + const year3 = year - 3; + const year4 = year - 4; + const year5 = year - 5; + return [ + { text: year1 + "년", val: year1.toString() }, + { text: year2 + "년", val: year2.toString() }, + { text: year3 + "년", val: year3.toString() }, + { text: year4 + "년", val: year4.toString() }, + { text: year5 + "년", val: year5.toString() }, + ] +} + +const selectSearchYear:SelectDataType[] = getYearData(); + +// 유량 기간 구분 +const selectFlowTerm : SelectDataType[] = [ + { text: "연간", val: '0' }, + { text: "하절기", val: '01' }, + { text: "동절기", val: '02' }, + { text: "청천시", val: '03' }, + { text: "강우시", val: '04' }, +]; + +// 유량 기간 구분 +const selectFlowPanel : SelectDataType[] = [ + { text: "전체", val: '0'}, + { text: "3mm미만", val: '1'}, + { text: "3~10mm미만", val: '2'}, + { text: "10~20mm미만", val: '3'}, + { text: "20~30mm미만", val: '4'}, + { text: "30~40mm미만", val: '5'}, + { text: "40~50mm미만", val: '6'}, + { text: "50~60mm미만", val: '7'}, + { text: "60mm이상", val: '8'}, +]; + +// 중권역 데이터 +const selectMiddleRegionData: SelectDataType[] = [ + { text: "전체", val: '0' } +]; + +/** + * STORE - API + */ + +// 시도 API 호출 함수 추가 +const fetchSidoData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/city'); + const data = res.data; + selectSidoData = [ + { text: "전체", val: '0'}, + ...data.map((item: sidoDataType) => ({ + text: item.sido || '', + val: String(item.adstDstrCd) || '' + })) + ]; + return selectSidoData; + } catch (error) { + console.error('시도 데이터를 가져오는데 실패했습니다:', error); + return selectSidoData; + } +}; + +// 시군구 API 호출 함수 추가 +const fetchSigunData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/sgg'); + const data = res.data; + selectSigunData = [ + { text: "전체", val: '0' }, + ...data + .map((item: sigunDataType) => ({ + text: item.gugun || '', + val: String(item.adstDstrCd) || '' + })) + ]; + return selectSigunData; + } catch (error) { + console.error('시군구 데이터를 가져오는데 실패했습니다:', error); + return selectSigunData; + } +}; + +// 권역 API 호출 함수 추가 +const fetchRegionData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/sarea'); + const data = res.data; + return [ + { text: "전체", val: '0'}, + ...data.map((item: regionDataType) => ({ + text: item.rvbsn || '', + val: String(item.rvbsnCd) || '' + })) + ]; + } catch (error) { + console.error('권역 데이터를 가져오는데 실패했습니다:', error); + return selectRegionData; + } +}; + +// 중권역 API 호출 함수 추가 +const fetchMiddleRegionData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/midArea'); + const data = res.data; + return [ + { text: "전체", val: '0' }, + ...data + .map((item: middleRegionDataType) => ({ + text: item.mdmbsn || '', + val: String(item.rvbsnCd) || '' + })) + ]; + } catch (error) { + console.error('중권역 데이터를 가져오는데 실패했습니다:', error); + return selectMiddleRegionData; + } +}; + +// 단위유역 API 호출 함수 추가 +const fetchUnitWaterShedData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/largeArea'); + const data = res.data; + return [ + { text: "전체", val: '0' }, + ...data + .map((item: unitWaterShedDataType) => ({ + text: item.lgbsn || '', + val: String(item.rvbsnCd) || '' + })) + ]; + } catch (error) { + console.error('단위유역 데이터를 가져오는데 실패했습니다:', error); + return selectUnitWaterShedData; + } +}; + +// 환경청 데이터 +const fetchEnvPartData = async (): Promise => { + try { + const res = await getFetch(fetchType.FO, '/addr/govArea'); + const data = res.data; + selectEnvPartData = [ + { text: "전체", val: '0', adstDliStrCd: '' }, + ...data + .map((item: envPartDataType) => ({ + text: item.lclgvrnmntNm || '', + val: String(item.lclgvrnmntCd) || '', + adstDliStrCd: item.adstDliStrCd || '' + })) + ]; + return selectEnvPartData; + } catch (error) { + console.error('환경청 데이터를 가져오는데 실패했습니다:', error); + return selectEnvPartData; + } +}; + +function sortMenus(items: MenuItem[]): MenuItem[] { + return [...items] + .sort((a, b) => a.sortSeq - b.sortSeq) + .map((item) => ({ + ...item, + children: item.children ? sortMenus(item.children) : [], + })); +} + +function isVisible(item: MenuItem): boolean { + return item.useYn !== "N" && item.otptYn !== "N"; +} + +function normalizeMenus(items: MenuItem[]): MenuItem[] { + return sortMenus(items) + .filter(isVisible) + .map((item) => ({ + ...item, + children: item.children ? normalizeMenus(item.children) : [], + })); +} + +export const REUSE_SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "DM"; +const anonymousMenuCache = new Map(); +const anonymousMenuInFlight = new Map>(); + +export async function fetchAnonymousMenu(siteId = REUSE_SITE_ID): Promise { + const cached = anonymousMenuCache.get(siteId); + if (cached) return cached; + + const inFlight = anonymousMenuInFlight.get(siteId); + if (inFlight) return inFlight; + + const request = (async () => { + const response = await fetch(`/cms-service/api/v1/menu/anonymous/user/${siteId}`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + cache: "no-store", + }); + + if (!response.ok) { + throw new Error(`Failed to load menu: ${response.status}`); + } + + const data = (await response.json()) as MenuItem[]; + const normalized = Array.isArray(data) ? normalizeMenus(data) : []; + anonymousMenuCache.set(siteId, normalized); + return normalized; + })(); + + anonymousMenuInFlight.set(siteId, request); + try { + return await request; + } finally { + anonymousMenuInFlight.delete(siteId); + } +} + +function isPathMatched(pathname: string, urlPath: string) { + if (!urlPath) return false; + return pathname === urlPath || pathname.startsWith(`${urlPath}/`); +} + +function hasMatchedPathInTree(menu: MenuItem, pathname: string): boolean { + if (isPathMatched(pathname, menu.urlPath)) return true; + return (menu.children ?? []).some((child) => hasMatchedPathInTree(child, pathname)); +} + +export function findActiveTopMenu(menus: MenuItem[], pathname: string): MenuItem | null { + const level1Menus = menus.filter((menu) => menu.levelNo === 1 || menu.parentId === null); + const matched = level1Menus.filter((menu) => hasMatchedPathInTree(menu, pathname)); + + return matched[0] ?? level1Menus[0] ?? null; +} + +export function hasAnyChild(item: MenuItem): boolean { + return Array.isArray(item.children) && item.children.length > 0; +} + +export function isMenuActive(pathname: string, menu: MenuItem): boolean { + return isPathMatched(pathname, menu.urlPath); +} +/**==================================== + * Store - expaort + ====================================*/ +export type CommonAction = { + initializeCommonData: () => Promise; + getMenuAuth: (menuCd:number) => Promise; + fetchAnonymousMenu: (siteId?: string) => Promise; +} + +export type CommonType = { + Common: { + isInit: boolean; + selectSidoData: SelectDataType[]; + selectSigunData: SelectDataType[]; + selectUpdownData: SelectDataType[]; + selectOperationData: SelectDataType[]; + selectFacilityPartData: SelectDataType[]; + selectSearchYear: SelectDataType[]; + selectUnitWaterShedData: SelectDataType[]; + selectRegionData: SelectDataType[]; + selectMiddleRegionData: SelectDataType[]; + selectEnvPartSidoData: SelectDataType[]; + selectEnvPartSigunData: SelectDataType[]; + selectFlowTerm: SelectDataType[]; + selectFlowPanel: SelectDataType[]; + selectEnvPartData: envPartSelectDataType[]; + btnClass: string; + } +} + +export const CommonState:CommonType = { + Common: { + isInit: false, + selectSidoData: selectSidoData, + selectSigunData: selectSigunData, + selectUpdownData: selectUpdownData, + selectOperationData: selectOperationData, + selectFacilityPartData: selectFacilityPartData, + selectSearchYear: selectSearchYear, + selectRegionData: selectRegionData, + selectUnitWaterShedData: selectUnitWaterShedData, + selectMiddleRegionData: selectMiddleRegionData, + selectEnvPartSidoData : selectEnvPartSidoData, + selectEnvPartSigunData : selectEnvPartSigunData, + selectFlowTerm: selectFlowTerm, + selectFlowPanel: selectFlowPanel, + selectEnvPartData : selectEnvPartData, + btnClass: "p-4 m-2 text-white rounded-sm text-lg font-medium" + } +} + +export const CommonReducer:(set:any)=>CommonAction=(set: any) => { + return { + getMenuAuth : async (menuCd : number) =>{ + const response = await getFetch( + "local", + `/cms-service/api/v1/menu-authrts/pageinfo/${menuCd}` + ); + set((state: CommonType) => { + return { + ...state, + auth: { + mngAuthrtYn: response[0].mngAuthrtYn, + inqAuthrtYn: response[0].mngAuthrtYn, + regAuthrtYn: response[0].mngAuthrtYn, + mdfcnAuthrtYn: response[0].mngAuthrtYn, + delAuthrtYn: response[0].mngAuthrtYn, + }, + }; + }); + }, + // 시도 변경 이벤트 + initializeCommonData: async () => { + const promiseSet = await Promise.all([fetchSidoData(), fetchSigunData(), fetchRegionData(), fetchUnitWaterShedData(), fetchMiddleRegionData(), fetchEnvPartData()]); + const [sidoData, sggData, regionData, unitData, midData, envData] = promiseSet; + set((state: CommonType) => ({ + Common: { + ...state.Common, + selectSidoData: sidoData, + selectSigunData: sggData, + selectRegionData: regionData, + selectMiddleRegionData: midData, + selectUnitWaterShedData: unitData, + selectEnvPartData: envData + } + }) + )}, + fetchAnonymousMenu: async (siteId = REUSE_SITE_ID) => fetchAnonymousMenu(siteId) + } +} +export type CommonStore = CommonType & CommonAction; diff --git a/apps/dm_new/app/store/index.tsx b/apps/dm_new/app/store/index.tsx new file mode 100644 index 000000000..47170b3db --- /dev/null +++ b/apps/dm_new/app/store/index.tsx @@ -0,0 +1,42 @@ +'use client' + +import { type ReactNode, createContext, useContext, useRef } from 'react' +import { useStore } from 'zustand' +import { type DmStore, StoreZus } from './store' + + +export type StoreApi = ReturnType + +export const StoreContext = createContext( + undefined, +) + +export interface StoreProviderProps { + children: ReactNode +} + +export const StoreProvider = ({ + children, +}: StoreProviderProps) => { + const storeRef = useRef(null) + if (!storeRef.current) { + // Create store instance if not initialized + storeRef.current = StoreZus() as StoreApi + } + return ( + + {children} + + ) +} + +export const useDmStore = ( + selector: (store: DmStore) => T, +): T => { + const storeContext = useContext(StoreContext) + if (!storeContext) { + throw new Error(`useStore must be used within StoreProvider`) + } + + return useStore(storeContext, selector) +} \ No newline at end of file diff --git a/apps/dm_new/app/store/map/index.tsx b/apps/dm_new/app/store/map/index.tsx new file mode 100644 index 000000000..f5b44c891 --- /dev/null +++ b/apps/dm_new/app/store/map/index.tsx @@ -0,0 +1,114 @@ +import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; +import { fetchType, getFetch } from "@/components/ui/api"; +import { DEV_URL_FO } from "@/components/ui/config"; +import { getCookie } from "@common/components/lib/cookie"; +import type { FacilityInfo, SubsInfo } from "./type"; + +const tokenId = getCookie(AUTH_USER_ID); +const Authorization = getCookie(CLAIM_NAME); + +const checkWrRegList = async () => { + const wrRegList = await getFetch(fetchType.WR, `/reuseFc/pubFc`); + return wrRegList; +}; + +const getRuseFclt = async ( + page: string, + sggOrCity: string, + searchTxt?: string +) => { + const checkWrRegListRes = await checkWrRegList(); + + let url = `/prcsFclt/ruseInfo/${page ? page : 1}/5/A/${sggOrCity ? sggOrCity : "0"}/0/1/SW`; + + const ruseList = checkWrRegListRes.data.filter((item: string) => item !== ""); + + if (searchTxt) { + url = url + `/${searchTxt}`; + } + + const response = await fetch(DEV_URL_FO + url, { + method: "POST", + headers: { + "Content-Type": "application/json", + [AUTH_USER_ID]: tokenId ?? "", + [CLAIM_NAME]: Authorization ?? "", + }, + body: JSON.stringify(ruseList), + }); + + const hasuList = await response.json().then((res: any) => { + const data = res.data + .map((item: any) => { + return { + ...item, + fctCpc: Number(item.fctCpc) || 0, + searchNm: item.fctNm, + searchPk: item.bscFctNm, + }; + }) + .filter((hasu:any) => ruseList.find((ruse:any) => hasu.bscFctCd === ruse)); + return { data: data, count: res.count }; + }); + + return hasuList; +}; + +export type DmMapActions = { + findRuseFclt(sggOrCity: string, searchTxt: string): void; + setSubsInfo(subsInfo: SubsInfo[]): void; +}; + +export type DmMapType = { + map: { + ruseFcltList: FacilityInfo[]; + subsInfo: SubsInfo[]; + }; +}; + +export const dmMapState: DmMapType = { + map: { + ruseFcltList: [], + // subsInfo: [ + // { + // ruseUtztnFcltNm: "수원제1,2 재이용1", // 재이용시설명 + // ruseUtztnFcltCd: "41110PB001R1", + // }, + // { + // ruseUtztnFcltNm: "낙양 재이용1", // 재이용시설명 + // ruseUtztnFcltCd: "41150SW001R1", + // }, + // { + // ruseUtztnFcltNm: "서남 재이용1", // 재이용시설명 + // ruseUtztnFcltCd: "11500PB001R1", + // }, + // ], + subsInfo:[] + }, +}; +export type DmMapStore = DmMapType & DmMapActions; + +export const DmMapReducer = (set: any): DmMapActions => { + return { + findRuseFclt: async (sggOrCity: string, searchTxt: string) => { + const ruseFcltList = await getRuseFclt("1", sggOrCity, searchTxt); + + set((state: DmMapType) => ({ + ...state, + map: { + ...state.map, + ruseFcltList: ruseFcltList.data, + }, + })); + }, + setSubsInfo: async (subsInfo: SubsInfo[]) => { + set((state: DmMapType) => ({ + ...state, + map: { + ...state.map, + subsInfo: subsInfo, + }, + })); + }, + }; +}; diff --git a/apps/dm_new/app/store/map/type.tsx b/apps/dm_new/app/store/map/type.tsx new file mode 100644 index 000000000..adcaf7f37 --- /dev/null +++ b/apps/dm_new/app/store/map/type.tsx @@ -0,0 +1,39 @@ +export type FacilityInfo= { + bscFctCd: string; // 기본시설 코드 + bscFctNm: string; // 기본시설명 + rvbsnCd: string; // 유역 코드 + rvbsn: string; // 유역명 + lgbsn: string; // 대권역명 + mdmbsn: string; // 중권역명 + rgnCd: string; // 관할청 코드 + rgnNm: string; // 관할청 명칭 + adstDstrCd: string; // 행정구역 코드 + sido: string; // 시도 + gugun: string; // 구군 + addr: string; // 주소 + fctCpc: string; // 시설용량 + efflWqltStndrdCd: string; // 방류수 수질기준 코드 + efflWqltStndrdNm: string; // 방류수 수질기준명 + trtFrfNm: string; // 처리공법명 + trtDstr: string; // 처리구역 + rnDt: string; // 사용개시일 (예: YYYYMMDD) + cmplDt: string; // 준공일자 (예: YYYYMMDD) + trstOprEtpNm: string; // 위탁운영기관명 + bscFctClsNm: string; // 기본시설 구분명 + sitArea: string; // 부지면적 + trtDstrArea: string; // 처리구역 면적 + swrTrtPopl: string; // 하수처리 인구 + mnstr: string; // 총괄 유역 + efflWtrrgn: string; // 방류수역 + rvNm: string; // 하천명 + bscFctClsCd: string; // 기본시설 구분 코드 + rvbsnNm: string; // 유역명 + tel: string; // 전화번호 + rswtrYn: string; // 재이용수 여부 ("Y" 또는 "N") + ruse: boolean; // 재이용 여부 (boolean) + } + + export type SubsInfo={ + ruseUtztnFcltNm: string; // 재이용시설명 + ruseUtztnFcltCd: string; // 재이용시설 코드 + } \ No newline at end of file diff --git a/apps/dm_new/app/store/readme.md b/apps/dm_new/app/store/readme.md new file mode 100644 index 000000000..0710aa69b --- /dev/null +++ b/apps/dm_new/app/store/readme.md @@ -0,0 +1,121 @@ +# Zustand store +## Zustand 등록방법 +**-- index.tsx --** +### context 등록 +기본적으로 createContext를 통해 react에 context를 생성해서 react에서 사용할 수 있도록 해야함. +``` js +//StoreZus는 내부적으로 사용하기 위한 store파일에서 따로 생성함. +export type StoreApi = ReturnType +//react에서 사용하기 위한 context 생성. +export const StoreContext = createContext( + undefined, +) +``` + +### component로 작성 +등록된 context를 component화 시키기 위한 작업 +```js +//외부에서 호출시 형태로 호출. +//app이나 home에서 전체적인 사용이 되도록 작성해야함. +export const StoreProvider = ({ + children, +}: StoreProviderProps) => { + const storeRef = useRef(null) + if (!storeRef.current) { + //StoreZus는 store.tsx를 봐야함. + storeRef.current = StoreZus() + } + return ( + + {children} + + ) +} +``` + +### store 등록 +react에서 사용하는 store에 현재 context 등록 +```js +export const useWrStore = ( + selector: (store: WrStore) => T, +): T => { + const storeContext = useContext(StoreContext) + if (!storeContext) { + throw new Error(`useStore must be used within StoreProvider`) + } + + return useStore(storeContext, selector) +} +``` + +## Zustand 사용방법 +**-- store.tsx, 폴더별 index.tsx --** +### 기본 store 생성 +index.tsx의 context에 사용되는 StoreZus를 추가하여 사용됨. + +외부에서 생성된 state, actions를 사용 가능한 구조 +```js +import { createStore } from 'zustand/vanilla'; +import { tableActionsExport, tableStateExport, type tableState, type tableType } from './publicReuseFaciltyInsert/index';//외부 파일 위치. +export type WrStore = tableType;//typescript이므로 사용되는 스크립트를 type1 & type2 형식으로 넣어야함. +//index.tsx에서 사용하는 store 생성. +export const StoreZus = (initState: tableState = tableStateExport) => { + return createStore()((set) => { + return { + //아래에서 사용하는 state & actions은 외부 폴더에서 가져다가 사용. + ...initState,//외부에 있는 기본 초기값 state + ...tableActionsExport(set)//외부에 있는 actions + } + }) +} +``` +### 외부 스토어 작성 방법 +외부 스토어는 export를 어떻게 할건지만 정하면됨. + +사용하는 store.tsx에서 import를 받기만 하면 사용가능. +#### 타입을 외부로 빼는 방법 +```js +export type tableState = { + isInit: boolean; + upHeadList: TableUpperProps[]; + publicReuseFacilityUper: PublicReuseFacility[]; +} +type tableActions = { + decrementList: () => void +} + +export type tableType = tableState & tableActions; +``` +#### 초기값 state를 외부로 빼는 방법 +```js +export const tableStateExport:tableState= { + isInit:false, + upHeadList: upHeadList, + publicReuseFacilityUper: publicReuseFacilityUper, +} +``` +#### actions를 외부로 빼는 방법 +```js +//tableActionsExport를 import 받아서 실행하여 return 된 값을 사용 +export const tableActionsExport:(set:any)=>tableActions= +//set을 통해 react store에 등록 +(set: any) => { + return { + //decrementList Object 형태로 전달 + decrementList: + //set 함수를 실행해서 react store에 등록 + ()=>set( + // store에 등록되는 함수 구조를 가지고 있음. + (state:tableState) => { + //actions 실질적인 로직 작성하는 곳. + return ( + { + upHeadList: state.upHeadList, + publicReuseFacilityUper: + state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1) + })} + ) + } +} +``` + diff --git a/apps/dm_new/app/store/sdmMng/index.tsx b/apps/dm_new/app/store/sdmMng/index.tsx new file mode 100644 index 000000000..393f2503b --- /dev/null +++ b/apps/dm_new/app/store/sdmMng/index.tsx @@ -0,0 +1,801 @@ +import { fetchType, getFetch, postFetch } from "@/components/ui/api"; +import type { SelectDataType } from "@common/business_components/ui"; +import type { TableUpperProps } from "../common"; +import toast from "react-hot-toast"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import { z } from "zod"; + +/**==================================== + * Store - 타입 +=====================================*/ + +export const sdmInfoFormSchema = z.object({ + sdmNm: z.string().min(1, "수요처를 선택해주세요"), + demandYmd: z.date().optional(), + demandQnty: z + .string() + .min(1, "요구수량을 입력해주세요") + .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), + usePurpose: z.string().optional(), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + /**요구수질 */ + reqWaterQlty: z.string().optional(), + grssClfg: z.number().nullish(), + tn: z.number().nullish(), + combResdch: z.number().nullish(), + tph: z.number().nullish(), + trbt: z.number().nullish(), + ph: z.string().nullish(), + bod: z.number().nullish(), + /**컬럼확인필요 */ + clIonQnt: z.number().nullish(), + smll: z.string(), + cdtnv: z.number().nullish(), + crmty: z.number().nullish(), + rmrk: z.string().optional(), + /* telno: z.string() + .min(8, "전화번호는 8자 이상 입력해주세요") + .max(12, "전화번호는 12자 이내로 입력해주세요") + .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ + candidateTitle: z + .array( + z.object({ + ruseUtztnFcltNm: z.string(), + ruseUtztnFcltCd: z.string(), + }) + ) + .optional(), +}).superRefine((data, ctx) => { + const disabledFields = disableRules[data.usePurpose ?? ""] || []; + // 모든 수질 관련 필드 + // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. + const allFields: (keyof typeof data)[] = [ + "tn", "combResdch", "tph", + "trbt", "ph", "bod", "clIonQnt", + "smll", "cdtnv", "crmty", + ]; + + const allFieldsName: Record = { + tn: "총질소(T-N)(mg/L)", + combResdch: "결합잔류염소(mg/L)", + tph: "총인(T-P)(mg/L)", + trbt: "탁도(NTU)", + ph: "수소이온농도(pH)", + bod: "생물학적 산소요구량(BOD)(mg/L)", + clIonQnt: "염화물(mgCL/L)", + smll: "냄새", + cdtnv: "전기전도도(㎲/cm)", + crmty: "색도(도)", + }; + allFields.forEach((field) => { + const isDisabled = disabledFields.includes(field); + if (!isDisabled) { + // disabled가 아닌 필드는 반드시 값이 있어야 함 + const value = data[field]; + if (typeof value === "string") { + if (value.trim().length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (typeof value === "number") { + if (value <= 0 || Number.isNaN(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (value == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } + }); +}); + +export type SdmInfoFormData = z.infer; + + +// 수요처 정보 +export type DemandCompBizType = { + index: number | ''; + dmdInfoId: string | ''; + dmdInfoFk: string | ''; + bzmnNo: string | ''; + dmdCoNm: string | ''; + zip: string | ''; + daddr: string | ''; + telno: string | ''; + rgtr: string | ''; + useUsgCd: string | ''; + ctpvCd: string | ''; + sggCd: string | ''; + dmdAplyPswd: string | ''; + wntUntprc: number | ''; + dmndTmng: Date | ''; + dmndQty: number | ''; + grssClfg: number | null //총대장균군수 + tn: number | null //총질소 + combResdch: number | null //결합잔류염소 + tph: number | null //총인 + trbt: number | null //탁도 + ph: string | ''; //수소이온농 + blgcOxygDmndQnt: number | null //생물학적산소요구량 + clIonQnt: number | null //염화물 + smll: string | ''; //냄새 + cdtnv: number | null; //전기전도도 + crmty: number | null //색도 + rmrk: string | ''; + reqDt: string | ''; + dmdInfoSn: number | ''; + status: string | ''; + splyCndtList: SupplyCandidate[] | [], + ruseUtztnFcltNm?: string | '' + +} + +// 공급 후보자 타입 정의 +export type SupplyCandidate = { + ruseUtztnFcltNm: string; + ruseUtztnFcltCd: string; +}; + + +export type DemandMatchings = { + splyFcltFk: string | ''; + splyFcltFlg: string | ''; + pbnstRuseFcltBizFk: number | ''; + splyBgngDay: string | ''; + splyEndDay: string | ''; + splyCpct: number | ''; + splyFmYn: string | ''; + useUsgCd: string | ''; + dmdInfoSn: number | ''; + splyCndtList: string | ''; + mtchnCmptnYmd: string | ''; + rvwCmptnYmd: string | ''; + regDt: string | ''; + rgtr: string | ''; + mdfcnDt: string | ''; + mdfr: string | ''; +} + +// 유사사례 +export type SimilarCaseType = { + ruseBizNm: string | ''; // 사업명 + bizBgngDate: string | ''; + bizEndDate: string | ''; + splyFcltKnd: string | ''; + splyFcltNm: string | ''; + splyFcltCpct: string | ''; + dmdFcltNm: string | ''; + dmdFcltAddr: string | ''; + ruseFcltCpct: string | ''; + ruseFcltPplLen: string | ''; + ruseFcltUsg: string | ''; + ruseFcltSplyCpct: string | ''; + ruseFcltPrcsMsrc: string | ''; + grnteWqUsg: string | ''; + grnteWqGrssClfg: string | ''; + grnteWqCombResdch: string | ''; + grnteWqTrbt: string | ''; + grnteWqBod: string | ''; + grnteWqSmll: string | ''; + grnteWqCrmty: string | ''; + grnteWqTn: string | ''; + grnteWqTp: string | ''; + grnteWqPh: string | ''; + grnteWqClIonQnt: string | ''; + grnteWqCndctStat: string | ''; + grnteWqRmrk: string | ''; + reqBizReuseFcltCst: string | ''; + reqBizPplCst: string | ''; + reqBizArchCst: string | ''; + reqBizMchnCst: string | ''; + reqBizMtrngCst: string | ''; + reqBizGisCst: string | ''; + reqBizCctvCst: string | ''; + reqBizCmcnCst: string | ''; + reqBizWstCst: string | ''; + reqBizDesignCst: string | ''; + reqBizSpvsCst: string | ''; + reqBizSbsdCst: string | ''; + reqBizEtc: string | ''; + reqBizGrssCst: string | ''; + prcmPlanNlep: string | ''; + prcmPlanLcep: string | ''; + prcmPlanBrdnCst: string | ''; + prcmPlanEcqrRt: string | ''; + prcmPlanGrssCst: string | ''; + clclnNlep: string | ''; + clclnLcep: string | ''; + clclnBrdnCst: string | ''; + clclnEcqrRt: string | ''; + clclnGrssCst: string | ''; +} + +// 사업비 상세정보 +export type MoneyDetailType = { + reqBizGrssCst: string | ''; + reqBizReuseFcltCst: string | ''; + reqBizPplCst: string | ''; + reqBizArchCst: string | ''; + reqBizMchnCst: string | ''; + reqBizMtrngCst: string | ''; + reqBizGisCst: string | ''; + reqBizCctvCst: string | ''; + reqBizCmcnCst: string | ''; + reqBizWstCst: string | ''; + reqBizDesignCst: string | ''; + reqBizSpvsCst: string | ''; + reqBizSbsdCst: string | ''; + reqBizEtc: string | ''; + + /** 재원조달계획(백만원원) */ + prcmPlanGrssCst: string | ''; + prcmPlanNlep: string | ''; + prcmPlanLcep: string | ''; + prcmPlanBrdnCst: string | ''; + prcmPlanEcqrRt: string | ''; + /** 사업비 정산(백만원) */ + clclnGrssCst: string | ''; + clclnNlep: string | ''; + clclnLcep: string | ''; + clclnBrdnCst: string | ''; + clclnEcqrRt: string | ''; +} +// 규칙 타입 선언 +export type DisableRulesType = Record; +/**==================================== + * Store - 데이터 + ====================================*/ +// 수요처 정보 +const demandCompList: DemandCompType[] = [ +] + + +const disableRules: DisableRulesType = { + "청소": ["tn", 'tph', "clIonQnt", 'cdtnv'], + "화장실용수": ["tn", 'tph', "clIonQnt", 'cdtnv'], + "세척": ["combResdch", "crmty", 'tph', 'tn', "clIonQnt", 'cdtnv'], + "살수": ["combResdch", "crmty", 'tph', 'tn', "clIonQnt", 'cdtnv'], + "조경": ["combResdch", "crmty", 'tph', 'cdtnv'], + "농업_간접용수": ["combResdch", "crmty", 'tn', 'tph', 'clIonQnt'], + "농업_직접용수": ["combResdch", "crmty", 'tn', 'tph', 'clIonQnt'], + "하천": ["combResdch", "trbt", "crmty", 'clIonQnt', 'cdtnv'], + '공업': [] +}; + +const defaultRules: Record = { + "청소": { + grssClfg: null, + combResdch: 0.2, + trbt: 2, + bod: 5, + crmty: 20, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + + }, + "화장실용수": { + grssClfg: null, + combResdch: 0.2, + trbt: 2, + bod: 5, + crmty: 20, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "세척": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + combResdch: null, + smll: '불쾌하지않을것', + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "살수": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + cdtnv: null, + }, + "조경": { + grssClfg: null, + trbt: 2, + bod: 5, + ph: "5.8~8.5", + smll: '불쾌하지않을것', + clIonQnt: 250, + combResdch: null, + crmty: null, + tn: null, + tph: null, + cdtnv: null, + }, + "농업_간접용수": { + grssClfg: null, + trbt: 5, + bod: 8, + ph: "5.8~8.5", + cdtnv: 2000, + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + }, + "농업_직접용수": { + grssClfg: null, + trbt: 2, + bod: 8, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + cdtnv: 700, + combResdch: null, + crmty: null, + tn: null, + tph: null, + clIonQnt: null, + }, + "하천": { + grssClfg: 1000, + bod: 5, + tn: 10, + tph: 0.5, + smll: '불쾌하지않을것', + ph: "5.8~8.5", + combResdch: null, + trbt: null, + crmty: null, + clIonQnt: null, + cdtnv: null + }, + '공업': { + grssClfg: null, + bod: null, + tn: null, + tph: null, + ph: "~", + combResdch: null, + smll: '불쾌하지않을것', + trbt: null, + crmty: null, + clIonQnt: null, + cdtnv: null + } +}; + +// 사용용도 +const useTypesData: SelectDataType[] = [ + { text: '청소', val: "청소" }, + { text: "화장실용수", val: "화장실용수" }, + { text: "세척", val: "세척" }, + { text: "살수", val: "살수" }, + { text: "조경", val: "조경" }, + { text: "농업_간접용수", val: "농업_간접용수" }, + { text: "농업_직접용수", val: "농업_직접용수" }, + { text: "하천", val: "하천" }, + { text: "공업", val: "공업" }, +]; + +// 유사사례 모달 헤더 +export const similarCaseModalHeader: TableUpperProps[] = [ + { id: "ruseBizNm", title: "사업명", row: 0, rowSpan: 2 }, + { id: "bizBgngDate", title: "사업시작일", row: 0, rowSpan: 2 }, + { id: "bizEndDate", title: "사업종료일", row: 0, rowSpan: 2 }, + { id: "A", title: "공급처", row: 0, colSpan: 3 }, + { id: "splyFcltKnd", title: "시설종류", row: 1 }, + { id: "splyFcltNm", title: "공급처명", row: 1 }, + { id: "splyFcltCpct", title: "시설용량\n(㎥/일)", row: 1 }, + { id: "B", title: "수요처", row: 0, colSpan: 2 }, + { id: "dmdFcltNm", title: "수요처명", row: 1 }, + { id: "dmdFcltAddr", title: "주소", row: 1 }, + { id: "C", title: "재이용시설", row: 0, colSpan: 5 }, + { id: "ruseFcltCpct", title: "시설용량\n(㎥/일)", row: 1 }, + { id: "ruseFcltPplLen", title: "관로\n(km)", row: 1 }, + { id: "ruseFcltUsg", title: "용도", row: 1 }, + { id: "ruseFcltSplyCpct", title: "공급량\n(㎥/일)", row: 1 }, + { id: "ruseFcltPrcsMsrc", title: "처리공법", row: 1 }, + { id: "D", title: "보증수질", row: 0, colSpan: 13 }, + { id: "grnteWqUsg", title: "용도별", row: 1 }, + { id: "grnteWqGrssClfg", title: "총대장균군수\n(개/100ml)", row: 1 }, + { id: "grnteWqCombResdch", title: "결합잔류염소\n(mg/L)", row: 1 }, + { id: "grnteWqTrbt", title: "탁도\n(NTU)", row: 1 }, + { id: "grnteWqBod", title: "생물학적산소요구량\n(BOD)\n(mg/L)", row: 1 }, + { id: "grnteWqSmll", title: "냄새\n(개/100ml)", row: 1 }, + { id: "grnteWqCrmty", title: "색도\n(도)", row: 1 }, + { id: "grnteWqTn", title: "총질소\n(T-N)\n(mg/L)", row: 1 }, + { id: "grnteWqTp", title: "총인\n(T-P)\n(mg/L)", row: 1 }, + { id: "grnteWqPh", title: "수소이온농도\n(pH)", row: 1 }, + { id: "grnteWqClIonQnt", title: "염화물\n(mgCL/L)", row: 1 }, + { id: "grnteWqCndctStat", title: "전기전도도\n(μS/cm)", row: 1 }, + { id: "grnteWqRmrk", title: "비고", row: 1 }, + { id: "reqBizGrssCst", title: "소요사업비\n(백만원)", row: 0, rowSpan: 2 }, +] + +// 유사사례 데이터 +const similarCaseList: SimilarCaseType[] = [ + +] + +// 사업비 상세정보 모달 헤더 +export const moneyDetailModalHeader: TableUpperProps[] = [ + { id: "A", title: "소요사업비(백만원)", row: 0, colSpan: 14 }, + { id: "reqBizGrssCst", title: "총", row: 1, rowSpan: 2 }, + { id: "B", title: "토목", row: 1, colSpan: 2 }, + { id: "reqBizReuseFcltCst", title: "재이용시설", row: 2 }, + { id: "reqBizPplCst", title: "관로", row: 2 }, + { id: "reqBizArchCst", title: "건축", row: 1, rowSpan: 2 }, + { id: "reqBizMchnCst", title: "기계", row: 1, rowSpan: 2 }, + { id: "reqBizMtrngCst", title: "전기,계측", row: 1, rowSpan: 2 }, + { id: "reqBizGisCst", title: "GIS/DB", row: 1, rowSpan: 2 }, + { id: "reqBizCctvCst", title: "CCTV", row: 1, rowSpan: 2 }, + { id: "reqBizCmcnCst", title: "시공 및 준공", row: 1, rowSpan: 2 }, + { id: "reqBizWstCst", title: "폐기물", row: 1, rowSpan: 2 }, + { id: "reqBizDesignCst", title: "설계비", row: 1, rowSpan: 2 }, + { id: "reqBizSpvsCst", title: "감리비", row: 1, rowSpan: 2 }, + { id: "reqBizSbsdCst", title: "시설 부대비", row: 1, rowSpan: 2 }, + { id: "reqBizEtc", title: "기타", row: 1, rowSpan: 2 }, + { id: "C", title: "재원조달계획(백만원)", row: 0, colSpan: 5 }, + { id: "prcmPlanGrssCst", title: "총", row: 1, rowSpan: 2 }, + { id: "prcmPlanNlep", title: "국비", row: 1, rowSpan: 2 }, + { id: "prcmPlanLcep", title: "지방비", row: 1, rowSpan: 2 }, + { id: "prcmPlanBrdnCst", title: "원인자부담", row: 1, rowSpan: 2 }, + { id: "prcmPlanEcqrRt", title: "국고비율", row: 1, rowSpan: 2 }, + { id: "D", title: "사업비 정산(백만원)", row: 0, colSpan: 5 }, + { id: "clclnGrssCst", title: "총", row: 1, rowSpan: 2 }, + { id: "clclnNlep", title: "국비", row: 1, rowSpan: 2 }, + { id: "clclnLcep", title: "지방비", row: 1, rowSpan: 2 }, + { id: "clclnBrdnCst", title: "원인자부담", row: 1, rowSpan: 2 }, + { id: "clclnEcqrRt", title: "국고비율", row: 1, rowSpan: 2 }, +] + +// 사업비 상세정보 +const moneyDetailList: MoneyDetailType[] = [ +] + +// 수요정보 신청현황 헤더 +export const upHeadSdmInfoStatus: TableUpperProps[] = [ + { id: "index", title: "No." }, + { id: "ctpvCd", title: "시도", row: 0, rowSpan: 2 }, + { id: "sggCd", title: "시군구", row: 0, rowSpan: 2 }, + { id: "dmdCoNm", title: "회사명", row: 0, rowSpan: 2 }, + { id: "daddr", title: "공장주소", row: 0, rowSpan: 2 }, + { id: "telno", title: "전화번호", row: 0, rowSpan: 2 }, + { id: "dmndTmng", title: "신청일자", row: 0, rowSpan: 2 }, + { id: "status", title: "진행상태", row: 0, rowSpan: 2 }, +] + +// 수요정보 신청현황 데이터 + +/**==================================== + * Store - Api +=====================================*/ +// 수요처 등록 +const postSdmData = async (data: DemandCompType) => { + try { + await postFetch("/reuseFc/request", data); + sessionStorage.setItem("user", JSON.stringify(data)); + toast.success("수요처 등록에 성공하였습니다."); + return true; + } catch (error) { + console.log(error); + toast.error("수요처 등록에 실패하였습니다."); + return false; + } +} + +// 수요정보 등록 +const postSdmInfoData = async (data: DemandCompBizType) => { + try { + await postFetch("/reuseFc/request/demandInfo", data); + toast.success('수요 정보 등록에 성공하였습니다.'); + } catch (error) { + console.log(error); + toast.error('수요 정보 등록에 실패하였습니다.'); + } +} + +const postSdmLoginData = async (data: DemandCompType) => { + try { + const res = await postFetch("/reuseFc/request/login", data); + const jsonData = await res.json(); + + const status = jsonData.status; + const loginData = jsonData.data; + let loginSuccess = false; + if (status === 200 && loginData != null) { + alert("수요처 로그인에 성공하였습니다."); + delete loginData.dmdAplyPswd; + delete loginData.rgtr; + sessionStorage.setItem("user", JSON.stringify(loginData)); + loginSuccess = true; + } else if (status === 401) { + alert("패스워드가 일치하지 않습니다."); + loginSuccess = false; + } else { + alert("등록된 수요처가 없습니다."); + loginSuccess = false; + } + + return loginSuccess; + } catch (error) { + console.log(error); + alert("수요처 로그인에 실패하였습니다."); + } +} + +const getDemandCompList = async (searchText: string, pageCnt: number) => { + try { + const url = '/reuseFc/request/' + (searchText ? searchText : '') + '/' + pageCnt; + const res = await getFetch(fetchType.WR, url); + return { + data: res?.data || [], + count: res?.count || 0 + } + } catch (error) { + console.error(error + ' 수요처 정보를 불러오지 못했습니다.'); + return { data: [], count: 0 }; + } +} + +const getSdmInfoStatusList = async (dmdInfoId: string) => { + try { + const url = '/reuseFc/request/' + dmdInfoId; + const res = await getFetch(fetchType.WR, url); + return res?.data || []; + } catch (error) { + console.error(error + ' 수요정보 신청현황을 불러오지 못했습니다.'); + return []; + } +} + +// 유사사례 조회 +const fetchGetSimilarCaseList = async (part: string, value: number | string) => { + try { + const url = '/reuseFc/pubFcRuseCase/dm/' + part + '/' + (value ? value : 0); + const res = await getFetch(fetchType.WR, url); + return res?.data || []; + } catch (error) { + console.error(error + ' 유사사례 조회에 실패하였습니다.'); + return []; + } +} + +/**==================================== + * Store - expaort + ====================================*/ +export type SdmActions = { + initializeSdmData: () => void; + insertSdmData: (data: DemandCompType) => Promise; + insertSdmInfoData: (data: DemandCompBizType) => Promise; + getDemandCompList: (searchText: string, pageCnt: number) => Promise; + actionLogin: (data: DemandCompType) => Promise; + getSdmInfoStatusList: (dmdInfoId: string) => Promise; + getSimilarCaseList: (part: string, value: number | string) => Promise; + actionLogout: () => Promise; +} + +export type SdmType = { + Sdm: { + isInit: boolean; + useTypesData: SelectDataType[]; + demandCompList: DemandCompType[]; + upHeadSimilarCaseList: TableUpperProps[]; + similarCaseList: SimilarCaseType[]; + upHeadMoneyDetailList: TableUpperProps[]; + moneyDetailList: MoneyDetailType[]; + + // 수요정보 신청현황 + sdmInfoStatusList: DemandCompBizType[]; + upHeadSdmInfoStatus: TableUpperProps[]; + defaultRules: Record + disableRules: DisableRulesType; + // 로그인관련 + successNextPage: boolean; + successLogin: boolean; + } +} + +export const SdmState: SdmType = { + Sdm: { + isInit: false, + useTypesData: useTypesData, + demandCompList: demandCompList, + upHeadSimilarCaseList: similarCaseModalHeader, + similarCaseList: similarCaseList, + upHeadMoneyDetailList: moneyDetailModalHeader, + moneyDetailList: moneyDetailList, + // 수요정보 신청현황 + sdmInfoStatusList: [], + defaultRules: defaultRules, + disableRules: disableRules, + upHeadSdmInfoStatus: upHeadSdmInfoStatus, + + // 로그인관련 + successNextPage: false, + successLogin: false, + } +} + +export const SdmReducer = (set: any): SdmActions => { + return { + initializeSdmData: async () => { + }, + + insertSdmData: async (data: DemandCompType) => { + const preRes = await getSdmInfoStatusList(data.dmdInfoId); + if (preRes?.dmdInfoId) { + toast.error('이미 등록된 수요처입니다.'); + return; + } + + const res = await postSdmData(data); + if (res) { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + successNextPage: true, + } + })) + } + }, + + insertSdmInfoData: async (data: DemandCompBizType) => { + postSdmInfoData(data); + }, + + // 수요처 정보 + getDemandCompList: async (searchText: string, pageCnt: number) => { + const res = await getDemandCompList(searchText, pageCnt); + if (res.data.length === 0) { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + demandCompList: [], + } + })) + return; + } else { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + demandCompList: res.data, + } + })) + } + }, + + // 수요정보 신청현황 + getSdmInfoStatusList: async (dmdInfoId: string) => { + const res = await getSdmInfoStatusList(dmdInfoId); + + if (res.length === 0) { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + sdmInfoStatusList: [], + } + })) + return; + } + //const sdmInfoStatusList : DemandCompBizType[] = []; + + const sdmInfoStatusList: DemandCompBizType[] = res.demandInfo.map((el: DemandCompBizType, index: number) => { + const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); + const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; + const ruseUtztnFcltNm = matchingInfo?.ruseUtztnFcltNm ? matchingInfo.ruseUtztnFcltNm : ''; + + + return ({ + ...el, + index: ++index, + bzmnNo: res.bzmnNo, + dmdCoNm: res.dmdCoNm, + dmdInfoId: res.dmdInfoId, + zip: res.zip || '', + daddr: res.daddr, + telno: res.telno.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'), + rgtr: res.rgtr, + ctpvCd: res.ctpvCd, + sggCd: res.sggCd, + useUsgCd: el.useUsgCd, + dmndTmng: el.dmndTmng, + dmdInfoSn: el.dmdInfoSn, + status: status, + splyCndtList: el.splyCndtList, + ruseUtztnFcltNm: ruseUtztnFcltNm || '' + }) + }) + + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + sdmInfoStatusList: sdmInfoStatusList, + } + })) + }, + + actionLogin: async (data: DemandCompType) => { + const loginSuccess = await postSdmLoginData(data); + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + successLogin: loginSuccess, + } + })) + }, + + // 유사사례 조회 + getSimilarCaseList: async (part: string, value: number | string) => { + const res = await fetchGetSimilarCaseList(part, value); + const similarCaseList = res.map((el: SimilarCaseType) => { + const maskedBizNm = el.ruseBizNm + ? el.ruseBizNm.replace(/^(\s*)^\S+/, "***") // 첫 단어(공백 전까지) 치환 + : el.ruseBizNm; + + return { + ...el, + ruseBizNm: maskedBizNm, + }; + }); + console.log(similarCaseList); + + if (similarCaseList && similarCaseList.length !== 0) { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + similarCaseList: similarCaseList, + } + })) + return; + } else { + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + similarCaseList: [], + } + })) + return; + } + }, + + actionLogout: async () => { + sessionStorage.removeItem("user"); + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + successLogin: false, + } + })) + } + } +} +export type SdmStore = SdmType & SdmActions; diff --git a/apps/dm_new/app/store/sdmMng/postcode-service.ts b/apps/dm_new/app/store/sdmMng/postcode-service.ts new file mode 100644 index 000000000..e8a791adb --- /dev/null +++ b/apps/dm_new/app/store/sdmMng/postcode-service.ts @@ -0,0 +1,290 @@ +export type AddrItem = { + zipNo: string; + roadAddr: string; + jibunAddr: string; + ctpvNm: string; + sggNm: string; +}; + +export interface ZipSearchParams { + roadNm?: string; + bldgNoMno?: number | null; + bldgNoSno?: number | null; + page?: number; + size?: number; +} + +export interface Zip { + zoneNo: string; + ctpvNm: string; + sggNm: string; + lotno: string; + zipAddr: string; +} + +interface ZipListResponse { + content?: Zip[]; + totalElements?: number; +} + +export type PostcodeSearchParams = { + roadNm: string; + bldgNoMno: number; + bldgNoSno: number | null; + page: number; +}; + +export type ParsePostcodeInputResult = + | { ok: true; value: PostcodeSearchParams; keyword: string } + | { ok: false; message: string }; + +export type PostcodeSearchResult = { + totalCount: number; + items: AddrItem[]; +}; + +type SuggestionSeed = { + roadNm: string; + bldgNoMno: number; +}; + +async function findAddress(searchQuery: ZipSearchParams): Promise { + if (!searchQuery) return {}; + + const params = new URLSearchParams(); + if (searchQuery.roadNm) params.append("roadNm", searchQuery.roadNm); + if (searchQuery.bldgNoMno != null) { + params.append("bldgNoMno", searchQuery.bldgNoMno.toString()); + } + if (searchQuery.bldgNoSno != null) { + params.append("bldgNoSno", searchQuery.bldgNoSno.toString()); + } + if (searchQuery.page != null) params.append("page", searchQuery.page.toString()); + if (searchQuery.size != null) params.append("size", searchQuery.size.toString()); + + const url = `/cms-service/api/v1/zips?${params.toString()}`; + const response = await fetch(url); + if (!response.ok) throw new Error("Network response was not ok"); + return (await response.json()) as ZipListResponse; +} + +function extractSuggestionSeed(item: Zip): SuggestionSeed | null { + const ext = item as Zip & { + roadNm?: string; + bldgNoMno?: number | string; + }; + + const mnoByField = Number(ext.bldgNoMno); + const roadNmByField = (ext.roadNm ?? "").trim(); + if (roadNmByField && Number.isInteger(mnoByField) && mnoByField > 0) { + return { roadNm: roadNmByField, bldgNoMno: mnoByField }; + } + + const firstSegment = (item.zipAddr ?? "").split(",")[0]?.trim() ?? ""; + if (!firstSegment) return null; + + const parts = firstSegment.split(/\s+/); + const bldgToken = parts.at(-1) ?? ""; + const matched = bldgToken.match(/^(\d+)(?:-\d+)?$/); + if (!matched) return null; + + const bldgNoMno = Number(matched[1]); + const roadNm = parts.slice(0, -1).join(" ").trim(); + if (!roadNm || !Number.isInteger(bldgNoMno) || bldgNoMno <= 0) { + return null; + } + + return { roadNm, bldgNoMno }; +} + +function searchFilter(search: string): boolean { + if (search.length === 0) return true; + + const expText = /[%=><]/; + if (expText.test(search)) { + alert("특수문자를 입력 할수 없습니다."); + return false; + } + + const sqlArray = [ + "OR", + "SELECT", + "INSERT", + "DELETE", + "UPDATE", + "CREATE", + "DROP", + "EXEC", + "UNION", + "FETCH", + "DECLARE", + "TRUNCATE", + ]; + + for (const sqlWord of sqlArray) { + const regex = new RegExp(sqlWord, "gi"); + if (regex.test(search)) { + alert(`"${sqlWord}"와(과) 같은 특정문자로 검색할 수 없습니다.`); + return false; + } + } + + return true; +} + +export function parsePostcodeInput( + input: string, + page = 1, +): ParsePostcodeInputResult { + const normalized = input.replace(/\s+/g, " ").trim(); + + if (!normalized) { + return { + ok: false, + message: "도로명과 건물번호를 입력해 주세요. 예) 테헤란로 152-1", + }; + } + + const parts = normalized.split(" "); + if (parts.length < 2) { + if (/^\d+(?:-\d+)?$/.test(normalized)) { + return { ok: false, message: "도로명을 입력해 주세요" }; + } + if (/[가-힣]/.test(normalized) && !/\d/.test(normalized)) { + return { ok: false, message: "건물번호를 입력해 주세요" }; + } + return { + ok: false, + message: "도로명과 건물번호를 공백으로 구분해 주세요. 예) 테헤란로 152-1", + }; + } + + const bldgToken = parts.at(-1) ?? ""; + const roadNm = parts.slice(0, -1).join(" ").trim(); + + if (!roadNm || !/[가-힣]/.test(roadNm)) { + return { + ok: false, + message: "도로명을 입력해 주세요", + }; + } + + if (!/\d/.test(bldgToken)) { + return { ok: false, message: "건물번호를 입력해 주세요" }; + } + + if (!/^\d+(?:-\d+)?$/.test(bldgToken)) { + return { + ok: false, + message: + "건물번호는 숫자 또는 숫자-숫자 형식으로 입력해 주세요. 예) 152 또는 152-1", + }; + } + + const [mnoStr, snoStr] = bldgToken.split("-"); + const bldgNoMno = Number(mnoStr); + const bldgNoSno = snoStr ? Number(snoStr) : null; + + if (!Number.isInteger(bldgNoMno) || bldgNoMno <= 0) { + return { ok: false, message: "건물본번은 1 이상의 숫자로 입력해 주세요." }; + } + + if (bldgNoSno !== null && (!Number.isInteger(bldgNoSno) || bldgNoSno <= 0)) { + return { ok: false, message: "건물부번은 1 이상의 숫자로 입력해 주세요." }; + } + + return { + ok: true, + value: { + roadNm, + bldgNoMno, + bldgNoSno, + page, + }, + keyword: `${roadNm} ${bldgNoMno}${bldgNoSno ? `-${bldgNoSno}` : ""}`, + }; +} + +export function extractSearchInputFromRoadAddr(roadAddr: string): string { + const firstSegment = roadAddr.split(",")[0]?.trim() ?? ""; + if (!firstSegment) return ""; + + const parts = firstSegment.split(/\s+/); + const bldgToken = parts.at(-1) ?? ""; + if (!/^\d+(?:-\d+)?$/.test(bldgToken)) return ""; + + return `${parts.slice(0, -1).join(" ")} ${bldgToken}`.trim(); +} + +export async function fetchPostcodeSuggestions(input: string): Promise { + const normalized = input.replace(/\s+/g, " ").trim(); + const parts = normalized.split(" "); + const tail = parts.at(-1) ?? ""; + const roadNm = + /^\d+(?:-\d+)?$/.test(tail) && parts.length > 1 + ? parts.slice(0, -1).join(" ").trim() + : normalized; + if (!roadNm) return []; + if (!searchFilter(roadNm)) return []; + + const data = await findAddress({ + roadNm, + page: 0, + size: 50, + }); + + const list = Array.isArray(data?.content) ? (data.content as Zip[]) : []; + const suggestions: string[] = []; + const seen = new Set(); + + for (const item of list) { + const seed = extractSuggestionSeed(item); + if (!seed) continue; + + const label = `${seed.roadNm} ${seed.bldgNoMno}`; + if (seen.has(label)) continue; + + seen.add(label); + suggestions.push(label); + if (suggestions.length >= 5) break; + } + + return suggestions; +} + +export async function fetchPostcode( + params: PostcodeSearchParams, +): Promise { + const keywordForCheck = `${params.roadNm} ${params.bldgNoMno}${ + params.bldgNoSno ? `-${params.bldgNoSno}` : "" + }`; + if (!searchFilter(keywordForCheck)) { + return { + totalCount: 0, + items: [], + }; + } + + const query: ZipSearchParams = { + roadNm: params.roadNm, + bldgNoMno: params.bldgNoMno, + bldgNoSno: params.bldgNoSno, + page: Math.max(params.page - 1, 0), + size: 20, + }; + + const data = await findAddress(query); + const list = Array.isArray(data?.content) ? (data.content as Zip[]) : []; + const items: AddrItem[] = list.map((item) => ({ + zipNo: item.zoneNo ?? "", + roadAddr: item.zipAddr ?? "", + jibunAddr: item.lotno ?? item.zipAddr ?? "", + ctpvNm: item.ctpvNm ?? "", + sggNm: item.sggNm ?? "", + })); + + return { + totalCount: Number(data?.totalElements ?? 0), + items, + }; +} diff --git a/apps/dm_new/app/store/store.tsx b/apps/dm_new/app/store/store.tsx new file mode 100644 index 000000000..25508bae2 --- /dev/null +++ b/apps/dm_new/app/store/store.tsx @@ -0,0 +1,48 @@ +import { createStore } from "zustand/vanilla"; +import { + CommonReducer, + CommonState, + type CommonStore, + type CommonType, +} from "./common"; +import { SdmReducer, SdmState, type SdmStore, type SdmType } from "./sdmMng"; +import { + dmMapState, + type DmMapType, + type DmMapStore, + DmMapReducer, +} from "./map"; +import { tokenManager } from "@/src/entities/auth/lib/token-manager"; +import { + DmStatusReducer, + dmStatusState, + type DmStatusStore, + type DmStatusType, +} from "./wrStatus"; + +export type DmStore = CommonStore & SdmStore & DmMapStore & DmStatusStore; +export type DmState = CommonType & SdmType & DmMapType & DmStatusType; + +export const StoreZus = ( + initState: DmState = { + ...CommonState, + ...SdmState, + ...dmMapState, + ...dmStatusState, + } +) => { + return createStore()((set) => { + if (typeof window !== "undefined") { + tokenManager.setToken("site-id-key", process.env.NEXT_PUBLIC_SITE_ID||"DM"); + tokenManager.setToken("site-theme-key", "green"); + } + + return { + ...initState, + ...CommonReducer(set), + ...SdmReducer(set), + ...DmMapReducer(set), + ...DmStatusReducer(set), + }; + }); +}; diff --git a/apps/dm_new/app/store/wrStatus/index.tsx b/apps/dm_new/app/store/wrStatus/index.tsx new file mode 100644 index 000000000..d3cc61cc4 --- /dev/null +++ b/apps/dm_new/app/store/wrStatus/index.tsx @@ -0,0 +1,129 @@ +import { fetchType, getFetch } from "@/components/ui/api"; +import type { + DmStatusDataType, + DmStatusChartType, + DmStatusBoardType, +} from "./type"; +import type { SelectDataType } from "../common"; +import { makeRegion } from "@/app/common/comFunction"; + +export type DmStatusActions = { + getDmStatusData(cityCd: SelectDataType[]): void; // 데이터의 연도 반환 +}; + +export type DmStatusType = { + dmStatus: { + year : string; + statusData: DmStatusDataType[]; + facilityTotals: string[]; + chartData: DmStatusChartType[]; + }; +}; + +export const dmStatusState: DmStatusType = { + dmStatus: { + year : '', + statusData: [], + facilityTotals: [], + chartData: [], + }, +}; +export type DmStatusStore = DmStatusType & DmStatusActions; + +export const DmStatusReducer = (set: any): DmStatusActions => { + return { + getDmStatusData: async (cityCd: SelectDataType[]) => { + const data = await getFetch(fetchType.FO, "/dashBoard/daemin"); + const year = data.data[0]?.year; // API 응답에서 year 값을 할당합니다. + console.log('cityCd ', cityCd) + const dataSum = data.data.reduce( + (acc: DmStatusDataType, item: DmStatusDataType) => { + acc.sewage = ( + parseInt(acc.sewage.replaceAll(",", "")) + + parseInt(item.sewage.replaceAll(",", "")) + ).toLocaleString(); + acc.rainwater = ( + parseInt(acc.rainwater.replaceAll(",", "")) + + parseInt(item.rainwater.replaceAll(",", "")) + ).toLocaleString(); + acc.reclaimed = ( + parseInt(acc.reclaimed.replaceAll(",", "")) + + parseInt(item.reclaimed.replaceAll(",", "")) + ).toLocaleString(); + acc.sewageCnt = ( + parseInt(acc.sewageCnt.replaceAll(",", "")) + + parseInt(item.sewageCnt.replaceAll(",", "")) + ).toLocaleString(); + acc.rainwaterCnt = ( + parseInt(acc.rainwaterCnt.replaceAll(",", "")) + + parseInt(item.rainwaterCnt.replaceAll(",", "")) + ).toLocaleString(); + acc.reclaimedCnt = ( + parseInt(acc.reclaimedCnt.replaceAll(",", "")) + + parseInt(item.reclaimedCnt.replaceAll(",", "")) + ).toLocaleString(); + return acc; + }, + { + region: "합계", + sewage: "0", + rainwater: "0", + reclaimed: "0", + sewageCnt: "0", + rainwaterCnt: "0", + reclaimedCnt: "0", + } + ); + data.data.unshift(dataSum); + + //console.log("dataSum ", dataSum); + const facilityTotals = [ + "하수재이용시설" + "(총" + dataSum?.sewageCnt + "개소)", + "빗물재이용시설" + "(총" + dataSum?.rainwaterCnt + "개소)", + "중수도재이용시설" + "(총" + dataSum?.reclaimedCnt + "개소)", + ]; + + const boardData: DmStatusBoardType[] = data.data.map( + (item: DmStatusDataType) => { + const regionText = item.sidoNm; //cityCd.find((city) => item.region === city.val); + const sewage = item.sewage; + const rainwater = item.rainwater; + const reclaimed = item.reclaimed; + const sewageCnt = item.sewageCnt; + const rainwaterCnt = item.rainwaterCnt; + const reclaimedCnt = item.reclaimedCnt; + + return { + region: regionText ? regionText : item.region, + sewage: sewageCnt + "(" + sewage.toLocaleString() + ")", + rainwater: rainwaterCnt + "(" + rainwater.toLocaleString() + ")", + reclaimed: reclaimedCnt + "(" + reclaimed.toLocaleString() + ")", + }; + } + ).filter((item): item is DmStatusBoardType => item !== undefined); + + //console.log("boardData ", boardData); + + const chartData = data.data.map((item) => ({ + label: makeRegion( + cityCd.find((city) => item.region === city.val)?.text ?? "" + ), + 하수: item.sewage, + 빗물: item.rainwater, + 중수도: item.reclaimed, + })); + + set((state: DmStatusType) => ({ + ...state, + dmStatus: { + ...state.dmStatus, + year : year, + statusData: boardData, + chartData: chartData, + facilityTotals: facilityTotals, + }, + })); + + }, + }; +}; diff --git a/apps/dm_new/app/store/wrStatus/type.ts b/apps/dm_new/app/store/wrStatus/type.ts new file mode 100644 index 000000000..2cf1b36f0 --- /dev/null +++ b/apps/dm_new/app/store/wrStatus/type.ts @@ -0,0 +1,41 @@ +export type DmStatusDataType = { + sidoNm: string; // 시도명 + region: string; // 시도코드 + sewage: string; + rainwater: string; + reclaimed: string; + sewageCnt:string; + rainwaterCnt:string; + reclaimedCnt:string; +}; + +export type DmStatusBoardType = { + sidoNm: string; // 시도명 + region: string; + sewage: string; + rainwater: string; + reclaimed: string; +}; + +export type DmStatusFcltType = { + sewageFclt: string; + rainwaterFclt: string; + reclaimedFclt: string; + thermalFclt: string; + }; + + + +export type DmStatusChartType = { + label: string, + 하수: string, + 빗물: string, + 중수도: string, +} + +export type RegionData = { + name: string; + x: number; + y: number; + values: number[]; + } \ No newline at end of file diff --git a/apps/dm_new/components.json b/apps/dm_new/components.json new file mode 100644 index 000000000..d9ef0ae53 --- /dev/null +++ b/apps/dm_new/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/dm_new/components/CandidateSiteModal.tsx b/apps/dm_new/components/CandidateSiteModal.tsx new file mode 100644 index 000000000..aa5a9c0ee --- /dev/null +++ b/apps/dm_new/components/CandidateSiteModal.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +export type CandidateSite = { + id: string; + name: string; + sewageM3d: number; // 하수처리량 + reuseM3d: number; // 재이용량 + // 지도 연동 시 사용(현재는 UI/구조용) + lat?: number; + lng?: number; +}; + +type Props = { + open: boolean; + onClose: () => void; + onApply: (selected: CandidateSite[]) => void; + initialSelected?: CandidateSite[]; + maxSelect?: number; // 기본 3개 +}; + +const MOCK_SITES: CandidateSite[] = [ + { id: "1", name: "서호생태 수자원 재이용1", sewageM3d: 1300, reuseM3d: 1300, lat: 35.0, lng: 128.0 }, + { id: "2", name: "서호생태 수자원 재이용2", sewageM3d: 1300, reuseM3d: 1300, lat: 35.01, lng: 128.01 }, + { id: "3", name: "서호생태 수자원 재이용3", sewageM3d: 1300, reuseM3d: 1300, lat: 35.02, lng: 128.02 }, +]; + +export default function CandidateSiteModal({ + open, + onClose, + onApply, + initialSelected = [], + maxSelect = 3, +}: Props) { + const [q, setQ] = useState(""); + const [selected, setSelected] = useState(initialSelected); + + // 지도 마커 컴포넌트 + function MapMarker({ label }: { label?: string }) { + return ( +
    + + + + + + + + + + + + + {/* pin body */} + + + {/* 내부 흰 원 */} + + + {label && ( + + {label} + + )} + +
    + ); + } + + // 모달 열릴 때마다 초기 선택값 동기화 + useEffect(() => { + if (open) setSelected(initialSelected); + if (open) setQ(""); + }, [open, initialSelected]); + + // ESC 닫기 + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [open, onClose]); + + const filtered = useMemo(() => { + const keyword = q.trim(); + if (!keyword) return MOCK_SITES; + return MOCK_SITES.filter((s) => s.name.includes(keyword)); + }, [q]); + + const isSelected = (id: string) => selected.some((s) => s.id === id); + + const toggleSelect = (item: CandidateSite) => { + setSelected((prev) => { + const exists = prev.some((p) => p.id === item.id); + if (exists) return prev.filter((p) => p.id !== item.id); + + if (prev.length >= maxSelect) return prev; // 최대 개수 초과 방지 + return [...prev, item]; + }); + }; + + const removeChip = (id: string) => setSelected((prev) => prev.filter((p) => p.id !== id)); + + if (!open) return null; + + return ( +
    + {/* overlay */} + +
    +
    + + {/* body */} +
    +
    + {/* left: search + table */} +
    +
    후보지 검색
    + +
    + setQ(e.target.value)} + placeholder="주소검색" + className="h-12 w-full rounded-sm border border-neutral-300 bg-white px-4 pr-12 text-sm outline-none focus:border-sky-500" + /> + +
    + + {/* table header */} +
    +
    +
    시설명
    +
    + 하수처리량
    (m³/일) +
    +
    + 재이용량
    (m³/일) +
    +
    선택
    +
    + + {/* rows */} +
    + {filtered.length === 0 ? ( +
    + 검색 결과가 없습니다. +
    + ) : ( + filtered.map((s, idx) => { + const picked = isSelected(s.id); + const disabled = !picked && selected.length >= maxSelect; + return ( +
    +
    + {idx + 1} + {s.name} +
    +
    {s.sewageM3d.toLocaleString()}
    +
    {s.reuseM3d.toLocaleString()}
    +
    + +
    +
    + ); + }) + )} +
    +
    + + {/*
    + 최대 {maxSelect}개까지 선택 가능합니다. +
    */} +
    + + {/* right: map */} +
    +
    + {/* TODO: 실제 지도 SDK 연동 시 이 영역 교체 */} +
    + 지도 영역(추후 지도 SDK 연동) +
    + + {/* 선택된 항목의 "가짜 마커" */} + {selected.map((s, i) => { + const POS = [ + { top: "40%", left: "45%" }, + { top: "25%", left: "75%" }, + { top: "70%", left: "80%" }, + ] as const; + + const pos = POS[i % POS.length] ?? POS[0]; + + return ( +
    + +
    + ); + })} +
    +
    +
    + + {/* selected chips */} +
    +
    + 선택된 시설 (시설명/하수처리량(m³/일)/재이용량(m³/일)) +
    + +
    + {selected.length === 0 ? ( +
    선택된 시설이 없습니다.
    + ) : ( +
    + {selected.map((s) => ( +
    + + {s.name} / {s.sewageM3d.toLocaleString()}(m³/일) / {s.reuseM3d.toLocaleString()}(m³/일) + + +
    + ))} +
    + )} +
    +
    + + {/* footer button */} +
    + +
    +
    +
    +
    + ); +} \ No newline at end of file diff --git a/apps/dm_new/components/CaseImageSlider.tsx b/apps/dm_new/components/CaseImageSlider.tsx new file mode 100644 index 000000000..41d8987b8 --- /dev/null +++ b/apps/dm_new/components/CaseImageSlider.tsx @@ -0,0 +1,175 @@ +// apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx +"use client"; + +import Image from "next/image"; +import { useEffect, useMemo, useRef, useState } from "react"; + +type Slide = { src: string; alt?: string }; + +type Props = { + slides: Slide[]; + className?: string; + maxWidthClassName?: string; // 예: "max-w-[760px]" +}; + +export default function CaseImageSlider({ + slides, + className = "", + maxWidthClassName = "max-w-[760px]", +}: Props) { + const safeSlides = useMemo(() => slides.filter(Boolean), [slides]); + const [idx, setIdx] = useState(0); + const [dragging, setDragging] = useState(false); + + const startX = useRef(0); + const deltaX = useRef(0); + + const total = safeSlides.length; + const canSlide = total > 1; + + const go = (dir: number) => { + if (!canSlide) return; + setIdx((prev) => { + const next = prev + dir; + if (next < 0) return total - 1; + if (next >= total) return 0; + return next; + }); + }; + + // ← → 키보드 지원(선택) + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (!canSlide) return; + if (e.key === "ArrowLeft") go(-1); + if (e.key === "ArrowRight") go(1); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [canSlide, total]); + + // 드래그(마우스/터치)로 넘기기 + const onPointerDown = (e: React.PointerEvent) => { + if (!canSlide) return; + setDragging(true); + startX.current = e.clientX; + deltaX.current = 0; + (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); + }; + + const onPointerMove = (e: React.PointerEvent) => { + if (!dragging) return; + deltaX.current = e.clientX - startX.current; + }; + + const onPointerUp = () => { + if (!dragging) return; + setDragging(false); + + const threshold = 60; // 넘김 감도 + if (deltaX.current > threshold) go(-1); + else if (deltaX.current < -threshold) go(1); + + deltaX.current = 0; + }; + + if (!total) return null; + + return ( +
    +
    +
    + {/* 좌측 버튼 */} + + + + {/* 우측 버튼 */} + + + + {/* 뷰포트 */} +
    +
    + {safeSlides.map((s, i) => ( +
    + {s.alt +
    + ))} +
    +
    +
    + + {/* 도트 */} + {canSlide && ( +
    + {safeSlides.map((_, i) => { + const active = i === idx; + return ( +
    + )} +
    +
    + ); +} diff --git a/apps/dm_new/components/CaseTabsCarousel.tsx b/apps/dm_new/components/CaseTabsCarousel.tsx new file mode 100644 index 000000000..ece8f0848 --- /dev/null +++ b/apps/dm_new/components/CaseTabsCarousel.tsx @@ -0,0 +1,578 @@ +"use client"; + +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import Image from "next/image"; + +/** + * CaseTabsCarousel.tsx (FINAL OPTIMIZED) + * - 탭별 데이터 + * - 카드 perPage(1/2/3) 반응형 + * - 카드 perPage개 = 1그룹, 좌/우 클릭 시 1그룹 단위 이동 + * - 3그룹 가상화(prev/cur/next)로 DOM 최소화 + * - px 기반 트랙 이동 + "측정 박스 분리"로 좌/우 비침 방지 + * - stale state 제거(방향/인덱스 ref 관리)로 슬라이드 안정성 강화 + * - BUGFIX 1) onTransitionEnd 버블링 방지 (target===currentTarget, transform만 처리) + * - BUGFIX 2) 슬라이드 중(busy) clipWidth 변화로 트랙 스냅(-W) 덮어쓰지 않게 분리 + */ + +type TabKey = "rain" | "reuse" | "sewage" | "global"; + +type CaseItem = { + id: string; + title: string; + desc: string; + imageSrc: string; + href?: string; +}; + +const TABS: { key: TabKey; label: string }[] = [ + { key: "rain", label: "빗물 재이용 사례" }, + { key: "reuse", label: "중수 재이용 사례" }, + { key: "sewage", label: "하수 재이용 사례" }, + { key: "global", label: "해외 재이용 사례" }, +]; + +// 실제 데이터로 교체해서 쓰세요(구조 유지) +const MOCK: Record = { + rain: [ + { + id: "rain-0", + title: "경기도 수원시 빗물이용과 중수도", + desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…", + imageSrc: "/images/landing/rain_01.png", + href: "/reuse/business/rain/suwon", + }, + // { + // id: "rain-2", + // title: "경기도 수원시 빗물이용과 중수도", + // desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기…", + // imageSrc: "/images/landing/rain_01.png", + // href: "#", + // }, + ], + reuse: Array.from({ length: 6 }).map((_, i) => ({ + id: `reuse-${i}`, + title: `중수 재이용 사례 ${i + 1}`, + desc: "설명 문구가 들어갑니다. (최대 2~3줄) …", + imageSrc: "/images/landing/sample_case.png", + href: "#", + })), + sewage: [ + { + id: "sewage-1", + title: "경기도 파주시 하수 재이용", + desc: "운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에...", + imageSrc: "/images/landing/sewage_01.png", + href: "/reuse/business/sewage/paju", + }, + { + id: "sewage-2", + title: "충남보령 하수재이용", + desc: "보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업...", + imageSrc: "/images/landing/sewage_02.png", + href: "/reuse/business/sewage/bolyeong", + }, + { + id: "sewage-3", + title: "여수시 하수처리수 재이용", + desc: "(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로...", + imageSrc: "/images/landing/sewage_03.png", + href: "/reuse/business/sewage/yeosu", + }, + { + id: "sewage-4", + title: "청주시 하수 재이용 시설", + desc: "청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO) 청주시 하수처리수 재이용 민간투자사업(BTO)...", + imageSrc: "/images/landing/sewage_04.png", + href: "/reuse/business/sewage/cheongju", + }, + { + id: "sewage-5", + title: "수원시 황구지천 하천유지용수", + desc: "황구지천 공공하수처리시설 하수처리수 재이용사업으로 향후 호매실천 및 금곡천 건천화 방지로...", + imageSrc: "/images/landing/sewage_05.png", + href: "/reuse/business/sewage/hwanggu", + }, + { + id: "sewage-6", + title: "구리시 도로자동청소시스템", + desc: "고농도 미세먼지 발생 및 폭염의 급증에 따라 공공하수처리시설 발생 재이용수 일부를 도로자동청소시스템에 활용...", + imageSrc: "/images/landing/sewage_06.png", + href: "/reuse/business/sewage/guri", + }, + // ...Array.from({ length: 6 }).map((_, i) => ({ + // id: `sewage-x-${i}`, + // title: `하수 재이용 사례 ${i + 4}`, + // desc: "설명 문구가 들어갑니다. (최대 2~3줄) …", + // imageSrc: "/images/landing/sample_case.png", + // href: "#", + // })), + ], + global: [ + { + id: "global-1", + title: "해외 재이용 사례 개요", + desc: "2024년 하수도 통계 기준 15.6%이며, 제2차 물 재이용 기본 계획을 고려한 2030년 목표는 17%, 우리나라의 하수 처리 재이용률은 15% 수준...", + imageSrc: "/images/landing/overview_01.png", + href: "reuse/business/globalCase/overview", + }, + { + id: "global-2", + title: "[대만] 국가 주도의 공업용수 공급", + desc: "대만은 물 재이용에 적극적이며 국가주도로 하수재이용 사업 시행(안핑), 안핑을 포함한 16건 공사 완료시 628,100 CMD 공급 가능...", + imageSrc: "/images/landing/taiwan_01.png", + href: "/reuse/business/globalCase/taiwan", + }, + { + id: "global-3", + title: "[호주] 생활 속 물 재이용 / 광역 물 재이용수 공급", + desc: "SEQ Water Grid는 브리즈번 남동부지역의 식수 공급을 위해 설정한 광역 수로망, 이 광역수로망을 활용하여 물이 부족한 지역으로 물을 공급...", + imageSrc: "/images/landing/australia_01.png", + href: "/reuse/business/globalCase/australia", + }, + { + id: "global-4", + title: "[싱가포르] 수요가 높은 산업의 물 재이용 의무화", + desc: "2003년 공식 NEWater 공장을 가동했으며 신뢰확보를 위해 NEWater Visitor Center를 함께 건설, NEWater 전체파이프 공급 → 반도체 공장에서 가장 많이 사용...", + imageSrc: "/images/landing/singapore_01.png", + href: "/reuse/business/globalCase/singapore", + }, + { + id: "global-5", + title: "[EU] 법률을 제정, EU국가에서 물 재이용 유도", + desc: "유럽 지역의 물 부족 문제를 인식하고 ’20년 물 재이용 법률을 제정 (2020/741), 2023년부터 모든 EU국가에서 시행...", + imageSrc: "/images/landing/eu_01.png", + href: "/reuse/business/globalCase/eu", + }, + ], +}; + +/* ---------------- utils ---------------- */ +function chunk(arr: T[], size: number) { + const res: T[][] = []; + if (size <= 0) return res; + for (let i = 0; i < arr.length; i += size) res.push(arr.slice(i, i + size)); + return res; +} +function mod(n: number, m: number) { + return ((n % m) + m) % m; +} +function usePrefersReducedMotion() { + const [reduced, setReduced] = useState(false); + useEffect(() => { + const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); + const onChange = () => setReduced(mq.matches); + onChange(); + mq.addEventListener?.("change", onChange); + return () => mq.removeEventListener?.("change", onChange); + }, []); + return reduced; +} + +/** + * wrapRef: perPage 계산용 + * clipRef: "패딩 없는 측정 박스" 폭(px) 계산용 + */ +function useCarouselMeasure() { + const wrapRef = useRef(null); + const clipRef = useRef(null); + + const [perPage, setPerPage] = useState(3); + const [clipWidth, setClipWidth] = useState(0); + + // perPage: container width로 반응형 결정 (rAF로 디바운스) + useEffect(() => { + const el = wrapRef.current; + if (!el) return; + + let raf = 0; + + const calc = () => { + const w = el.clientWidth; + if (w < 640) setPerPage(1); + else if (w < 1024) setPerPage(2); + else setPerPage(3); + }; + + const ro = new ResizeObserver(() => { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(calc); + }); + + calc(); + ro.observe(el); + return () => { + cancelAnimationFrame(raf); + ro.disconnect(); + }; + }, []); + + // clipWidth: 패딩 없는 박스에서 width를 정확히 측정 (sub-pixel 반올림) + useEffect(() => { + const el = clipRef.current; + if (!el) return; + + let raf = 0; + + const calc = () => { + const w = Math.round(el.getBoundingClientRect().width); + setClipWidth(w); + }; + + const ro = new ResizeObserver(() => { + cancelAnimationFrame(raf); + raf = requestAnimationFrame(calc); + }); + + calc(); + ro.observe(el); + return () => { + cancelAnimationFrame(raf); + ro.disconnect(); + }; + }, []); + + return { wrapRef, clipRef, perPage, clipWidth }; +} + +/* ---------------- component ---------------- */ +export default function CaseTabsCarousel() { + const [active, setActive] = useState("rain"); + + // 탭별 마지막 그룹 인덱스 기억 + const tabIndexRef = useRef>({ + rain: 0, + reuse: 0, + sewage: 0, + global: 0, + }); + + const reducedMotion = usePrefersReducedMotion(); + const { wrapRef, clipRef, perPage, clipWidth } = useCarouselMeasure(); + + const items = useMemo(() => MOCK[active] ?? [], [active]); + const groups = useMemo(() => chunk(items, perPage), [items, perPage]); + const groupCount = groups.length; + + const [cur, setCur] = useState(0); + const curRef = useRef(0); + + // px 기반 트랙 + const [trackPx, setTrackPx] = useState(0); + const [anim, setAnim] = useState(false); + const [busy, setBusy] = useState(false); + + // 방향 ref (onTransitionEnd에서 trackPx 비교하지 않음) + const dirRef = useRef<-1 | 1 | 0>(0); + + // cur 최신값 유지 + useEffect(() => { + curRef.current = cur; + }, [cur]); + + /** + * 리셋 로직 분리: + * - 탭/perPage/groupCount 바뀔 때만 "인덱스/상태" 리셋 + * - clipWidth 변화는 별도 effect에서 "busy 아닐 때만" 중앙(-W)로 배치 + */ + useLayoutEffect(() => { + const saved = tabIndexRef.current[active] ?? 0; + const nextCur = groupCount ? mod(saved, groupCount) : 0; + + dirRef.current = 0; + setCur(nextCur); + setBusy(false); + setAnim(false); + }, [active, perPage, groupCount]); + + // clipWidth 변화로 트랙 위치를 잡되, 슬라이드 중(busy)엔 덮어쓰지 않음 + useLayoutEffect(() => { + if (busy) return; + if (clipWidth > 0) setTrackPx(-clipWidth); + else setTrackPx(0); + }, [clipWidth, busy]); + + // 3그룹 가상화 + const viewGroups = useMemo(() => { + if (!groupCount) return [[], [], []] as CaseItem[][]; + return [ + groups[mod(cur - 1, groupCount)] ?? [], + groups[mod(cur, groupCount)] ?? [], + groups[mod(cur + 1, groupCount)] ?? [], + ]; + }, [groups, cur, groupCount]); + + const canSlide = groupCount > 1 && clipWidth > 0; + + const go = useCallback( + (dir: -1 | 1) => { + if (!canSlide) return; + if (busy) return; + + dirRef.current = dir; + + setBusy(true); + setAnim(!reducedMotion); + + // 중앙(-W) 기준: prev=0, next=-2W + setTrackPx(dir === 1 ? -2 * clipWidth : 0); + + // reduced motion이면 즉시 확정 + if (reducedMotion) { + const nextCur = mod(curRef.current + dir, groupCount); + tabIndexRef.current[active] = nextCur; + + setCur(nextCur); + setAnim(false); + setTrackPx(-clipWidth); + setBusy(false); + dirRef.current = 0; + } + }, + [active, busy, canSlide, clipWidth, groupCount, reducedMotion] + ); + + // BUGFIX 1) 자식 transitionend 버블링 방지 + transform만 처리 + const onTransitionEnd = useCallback( + (e: React.TransitionEvent) => { + // 자식 transitionend 버블링 방지 + if (e.target !== e.currentTarget) return; + if (e.propertyName !== "transform") return; + + if (!busy || reducedMotion) return; + + const dir = dirRef.current; + if (dir !== 1 && dir !== -1) return; + + const nextCur = mod(curRef.current + dir, groupCount); + tabIndexRef.current[active] = nextCur; + + // 1) 먼저 transition을 끄고(중요) + setAnim(false); + + // 2) cur를 회전 + 트랙을 즉시 중앙(-W)로 스냅 + // (이 순간은 "무조건 애니메이션 없이" 처리되어야 함) + setCur(nextCur); + setTrackPx(-clipWidth); + + // 3) 다음 프레임(2번 rAF)에서 다시 애니메이션 켜고 busy 해제 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setAnim(!reducedMotion); + setBusy(false); + dirRef.current = 0; + }); + }); + }, + [active, busy, clipWidth, groupCount, reducedMotion] +); + + const goDot = useCallback( + (idx: number) => { + if (!groupCount) return; + if (busy) return; + + const target = mod(idx, groupCount); + tabIndexRef.current[active] = target; + + dirRef.current = 0; + setCur(target); + setAnim(false); + if (clipWidth > 0) setTrackPx(-clipWidth); + }, + [active, busy, clipWidth, groupCount] + ); + + const trackStyle: React.CSSProperties = useMemo( + () => ({ + transform: `translate3d(${trackPx}px, 0, 0)`, + transition: anim ? "transform 700ms cubic-bezier(.22,.61,.36,1)" : "none", + willChange: busy ? "transform" : undefined, + }), + [trackPx, anim, busy] + ); + + return ( +
    + {/* 탭 바 */} +
    +
    +
    + {TABS.map((t) => { + const isActive = t.key === active; + + return ( + + ); + })} +
    +
    +
    + + {/* 슬라이더 */} +
    +
    + {/* 좌우 버튼 */} + + + + + {/* 중요: clip(가림)과 measure(측정) 분리 */} +
    +
    + {/* 측정은 패딩 없는 박스에서 */} +
    + {/* track */} +
    + {viewGroups.map((group, gi) => ( +
    + {/* 그룹 내부: perPage 컬럼 */} +
    + {(group ?? []).map((c) => ( + + ))} +
    +
    + ))} +
    +
    +
    +
    + + {/* 도트 */} +
    + {Array.from({ length: Math.max(groupCount, 1) }).map((_, i) => ( +
    +
    +
    +
    + ); +} diff --git a/apps/dm_new/components/ImageSlider.tsx b/apps/dm_new/components/ImageSlider.tsx new file mode 100644 index 000000000..41d8987b8 --- /dev/null +++ b/apps/dm_new/components/ImageSlider.tsx @@ -0,0 +1,175 @@ +// apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx +"use client"; + +import Image from "next/image"; +import { useEffect, useMemo, useRef, useState } from "react"; + +type Slide = { src: string; alt?: string }; + +type Props = { + slides: Slide[]; + className?: string; + maxWidthClassName?: string; // 예: "max-w-[760px]" +}; + +export default function CaseImageSlider({ + slides, + className = "", + maxWidthClassName = "max-w-[760px]", +}: Props) { + const safeSlides = useMemo(() => slides.filter(Boolean), [slides]); + const [idx, setIdx] = useState(0); + const [dragging, setDragging] = useState(false); + + const startX = useRef(0); + const deltaX = useRef(0); + + const total = safeSlides.length; + const canSlide = total > 1; + + const go = (dir: number) => { + if (!canSlide) return; + setIdx((prev) => { + const next = prev + dir; + if (next < 0) return total - 1; + if (next >= total) return 0; + return next; + }); + }; + + // ← → 키보드 지원(선택) + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (!canSlide) return; + if (e.key === "ArrowLeft") go(-1); + if (e.key === "ArrowRight") go(1); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [canSlide, total]); + + // 드래그(마우스/터치)로 넘기기 + const onPointerDown = (e: React.PointerEvent) => { + if (!canSlide) return; + setDragging(true); + startX.current = e.clientX; + deltaX.current = 0; + (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); + }; + + const onPointerMove = (e: React.PointerEvent) => { + if (!dragging) return; + deltaX.current = e.clientX - startX.current; + }; + + const onPointerUp = () => { + if (!dragging) return; + setDragging(false); + + const threshold = 60; // 넘김 감도 + if (deltaX.current > threshold) go(-1); + else if (deltaX.current < -threshold) go(1); + + deltaX.current = 0; + }; + + if (!total) return null; + + return ( +
    +
    +
    + {/* 좌측 버튼 */} + + + + {/* 우측 버튼 */} + + + + {/* 뷰포트 */} +
    +
    + {safeSlides.map((s, i) => ( +
    + {s.alt +
    + ))} +
    +
    +
    + + {/* 도트 */} + {canSlide && ( +
    + {safeSlides.map((_, i) => { + const active = i === idx; + return ( +
    + )} +
    +
    + ); +} diff --git a/apps/dm_new/components/Lnb.tsx b/apps/dm_new/components/Lnb.tsx new file mode 100644 index 000000000..b3861fbe0 --- /dev/null +++ b/apps/dm_new/components/Lnb.tsx @@ -0,0 +1,181 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useEffect, useMemo, useState } from "react"; +import { + fetchAnonymousMenu, + findActiveTopMenu, + hasAnyChild, + isMenuActive, + type MenuItem, +} from "@/app/store/common"; + +export type LnbItem = { + label: string; + href?: string; + children?: LnbItem[]; +}; + +function findOpenMenuCode(items: MenuItem[], pathname: string): number | null { + const matched = items.find((item) => { + if (isMenuActive(pathname, item)) return true; + return (item.children ?? []).some((child) => isMenuActive(pathname, child)); + }); + + return matched?.menuCd ?? null; +} + +export default function Lnb() { + const pathname = usePathname(); + const [menus, setMenus] = useState([]); + const [openMenuCode, setOpenMenuCode] = useState(null); + + useEffect(() => { + let isMounted = true; + + (async () => { + try { + const loaded = await fetchAnonymousMenu(); + if (!isMounted) return; + console.log("Fetched LNB menus:", loaded); + setMenus(loaded); + } catch (error) { + console.error("Failed to load DN LNB menu", error); + if (!isMounted) return; + setMenus([]); + } + })(); + + return () => { + isMounted = false; + }; + }, []); + + const currentTopMenu = useMemo( + () => findActiveTopMenu(menus, pathname), + [menus, pathname], + ); + + const secondLevelMenus = useMemo( + () => currentTopMenu?.children ?? [], + [currentTopMenu], + ); +// console.log("LNB render", { pathname, currentTopMenu, secondLevelMenus, menus }); + useEffect(() => { + const defaultOpen = findOpenMenuCode(secondLevelMenus, pathname); + setOpenMenuCode(defaultOpen); + }, [pathname, secondLevelMenus]); + + return ( + + ); +} diff --git a/apps/dm_new/components/LoginModal.tsx b/apps/dm_new/components/LoginModal.tsx new file mode 100644 index 000000000..60f46f7ad --- /dev/null +++ b/apps/dm_new/components/LoginModal.tsx @@ -0,0 +1,341 @@ +"use client"; + +import { useEffect, useId, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useDmStore } from "@/app/store"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import { sha512 } from 'js-sha512'; + +type LoginModalProps = { + open: boolean; + onClose: () => void; + onSuccess?: (payload: { userName: string; accessToken?: string }) => void; +}; + +type FieldKey = "bizNo" | "workerNo" | "pw"; + +const REGISTER_HREF = "/apply/dmdco"; + +function isBizNoValid(v: string) { + return /^\d{10}$/.test(v); +} + +function inputCls(opts: { hasError?: boolean; disabled?: boolean }) { + const base = "h-[48px] w-full rounded-[4px] border px-5 text-[18px] outline-none"; + if (opts.disabled) return `${base} border-gray-200 bg-gray-50 text-gray-400`; + if (opts.hasError) return `${base} border-red-500 ring-1 ring-red-400 focus:border-red-500`; + return `${base} border-gray-300 focus:border-sky-400`; +} + +export default function LoginModal({ open, onClose, onSuccess }: LoginModalProps) { + const titleId = useId(); + const router = useRouter(); + const { actionLogin } = useDmStore((state) => state); + + const [bizNo, setBizNo] = useState(""); // 사업자번호 + const [workerNo, setWorkerNo] = useState(""); // 종사업자번호 + const [noWorker, setNoWorker] = useState(true); // 종사업자번호 해당없음 checked + const [pw, setPw] = useState(""); + + // ✅ 유효성/서버 에러 표시용 + const [errors, setErrors] = useState>>({}); + const [touched, setTouched] = useState>>({}); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); // ESC 키로 모달 닫기 + }; + window.addEventListener("keydown", onKeyDown); + + // 스크롤 잠금 + const prev = document.body.style.overflow; + document.body.style.overflow = "hidden"; // 모달 오픈 시 배경 스크롤 방지 + + // 모달 오픈 시 에러/터치 초기화 + setErrors({}); + setTouched({}); + + return () => { + window.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prev; + }; + }, [open, onClose]); + + useEffect(() => { + if (noWorker) { // 종사업자번호 해당없음 + setWorkerNo(""); // 종사업자번호 + setErrors((p) => { + const nextErrors = { ...p }; + delete nextErrors.workerNo; + return nextErrors; + }); + } + }, [noWorker]); + + if (!open) return null; + + const validate = () => { + const next: Partial> = {}; + + const b = bizNo.trim(); + const w = workerNo.trim(); + const p = pw.trim(); + + if (!b) next.bizNo = "사업자등록번호를 입력해주세요."; + else if (!isBizNoValid(b)) + next.bizNo = "사업자등록번호는 10자리 숫자만 입력 가능합니다."; + + if (!noWorker && !w) next.workerNo = "종사업자등록번호를 입력해주세요."; + else if (!noWorker && !/^\d{4}$/.test(w)) + next.workerNo = "종사업자등록번호는 4자리 숫자만 입력 가능합니다."; + + if (!p) next.pw = "비밀번호를 입력해주세요."; + else if (p.length < 8) next.pw = "비밀번호는 8자 이상 입력해주세요."; + else if (p.length > 16) next.pw = "비밀번호는 16자 이내로 입력해주세요."; + + setErrors(next); + return next; + }; + + const formatBusinessNumber = (value: string) => { + // 숫자만 추출 + const digitsOnly = value.replace(/\D/g, ""); + + // 10자리까지만 처리 + const limitedDigits = digitsOnly.slice(0, 10); + + // 3-2-5 형식으로 포맷팅 + if (limitedDigits.length <= 3) { + return limitedDigits; + } else if (limitedDigits.length <= 5) { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3)}`; + } else { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; + } + }; + + const markTouched = (key: FieldKey) => + setTouched((p) => ({ ...p, [key]: true })); + + const markAllTouched = () => + setTouched({ bizNo: true, workerNo: true, pw: true }); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + markAllTouched(); + + const v = validate(); + if (Object.keys(v).length) return; //검증 에러가 하나라도 있으면 로그인 요청을 중단 + + try { + setSubmitting(true); + const param = { + dmdInfoId: bizNo + (noWorker ? "0000" : workerNo), // 종사업자번호 해당없음 "0000"으로 채움 + dmdAplyPswd: sha512.hex(pw), + }; + await actionLogin(param as DemandCompType); + + const sessionUser = sessionStorage.getItem("user"); + if (sessionUser) { + const parsed = JSON.parse(sessionUser) as { dmdCoNm?: string }; + onSuccess?.({ userName: parsed.dmdCoNm ?? "수요처" }); + onClose(); + router.refresh(); + } + } finally { + setSubmitting(false); + } + }; + + const initPassword = () => { + alert("관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다."); + } + + return ( +
    + {/* Backdrop */} + + +

    + 수요처 로그인 +

    + + {/* title underline gradient bar */} +
    +
    + + {/* Body */} +
    +
    + {/* 사업자 등록번호 */} +
    + + { + setBizNo(e.target.value.replace(/\D/g, "").slice(0, 10)); + setErrors((p) => ({ ...p, bizNo: undefined })); + }} + onBlur={() => { + markTouched("bizNo"); + validate(); + }} + placeholder="사업자등록번호를 입력하세요. (예: 123-45-67890)" + className={inputCls({ hasError: !!(touched.bizNo && errors.bizNo) })} + /> + {touched.bizNo && errors.bizNo && ( +

    + {errors.bizNo} +

    + )} +
    + + {/* 종사자등록번호 + 해당없음 */} +
    +
    + + + +
    + + { + setWorkerNo(e.target.value.replace(/\D/g, "").slice(0, 4)); + setErrors((p) => ({ ...p, workerNo: undefined })); + }} + onBlur={() => { + markTouched("workerNo"); + validate(); + }} + disabled={noWorker} + placeholder="종사자등록번호를 입력하세요." + className={inputCls({ + disabled: noWorker, + hasError: !noWorker && !!(touched.workerNo && errors.workerNo), + })} + /> + + {!noWorker && touched.workerNo && errors.workerNo && ( +

    + {errors.workerNo} +

    + )} +
    + + {/* 비밀번호 */} +
    + + { + setPw(e.target.value); + setErrors((p) => ({ ...p, pw: undefined })); + }} + onBlur={() => { + markTouched("pw"); + validate(); + }} + placeholder="비밀번호를 입력하세요." + className={inputCls({ hasError: !!(touched.pw && errors.pw) })} + /> + {touched.pw && errors.pw && ( +

    + {errors.pw} +

    + )} +
    + + {/* 로그인 버튼 */} + + + {/* 비밀번호 초기화 요청 */} + + + {/* 하단 안내 박스 + 수요처등록 버튼 */} +
    +
    +
    + 수요처를 등록하지 않았다면 +
    +
    + 먼저 수요처등록을 해주세요. +
    +
    + + +
    +
    +
    +
    +
    +
+ ); +} diff --git a/apps/dm_new/components/LoginStatus.tsx b/apps/dm_new/components/LoginStatus.tsx new file mode 100644 index 000000000..4ddc08812 --- /dev/null +++ b/apps/dm_new/components/LoginStatus.tsx @@ -0,0 +1,51 @@ +// apps/dm/app/(landing)/reuse/_components/LoginStatus.tsx +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function LoginStatus() { + const router = useRouter(); + const [userName, setUserName] = useState(null); + + useEffect(() => { + // 예시: 로그인 시 저장한 값 + const name = localStorage.getItem("userName"); + setUserName(name); + }, []); + + const handleLogout = () => { + localStorage.removeItem("accessToken"); + localStorage.removeItem("userName"); + router.refresh(); // 헤더 상태 갱신 + }; + + // 🔹 로그인 전 + if (!userName) { + return ( + + 로그인 + → + + ); + } + + // 🔹 로그인 후 + return ( +
+ + {userName}님 환영합니다. + + +
+ ); +} diff --git a/apps/dm_new/components/PostcodeModal.tsx b/apps/dm_new/components/PostcodeModal.tsx new file mode 100644 index 000000000..116bf91d8 --- /dev/null +++ b/apps/dm_new/components/PostcodeModal.tsx @@ -0,0 +1,303 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { + fetchPostcode, + fetchPostcodeSuggestions, + parsePostcodeInput, + type AddrItem, + type PostcodeSearchResult, +} from "@/app/store/sdmMng/postcode-service"; + +export type { AddrItem } from "@/app/store/sdmMng/postcode-service"; + +type Props = { + open: boolean; + onClose: () => void; + onSelect: (item: AddrItem) => void; +}; + +function useDebouncedValue(value: T, delay = 250) { + const [v, setV] = useState(value); + useEffect(() => { + const t = setTimeout(() => setV(value), delay); + return () => clearTimeout(t); + }, [value, delay]); + return v; +} + +export default function PostcodeModal({ open, onClose, onSelect }: Props) { + const [mounted, setMounted] = useState(false); + + const [q, setQ] = useState(""); + const dq = useDebouncedValue(q, 250); + + const [items, setItems] = useState([]); + const [suggestions, setSuggestions] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [searchMessage, setSearchMessage] = useState(""); + + const inputRef = useRef(null); + + useEffect(() => setMounted(true), []); + + useEffect(() => { + if (open) return; + setQ(""); + setItems([]); + setSuggestions([]); + setTotal(0); + setLoading(false); + setSearchMessage(""); + }, [open]); + + useEffect(() => { + if (!open) return; + + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + setTimeout(() => inputRef.current?.focus(), 50); + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + + return () => { + document.body.style.overflow = prevOverflow; + window.removeEventListener("keydown", onKeyDown); + }; + }, [open, onClose]); + + useEffect(() => { + if (!open) return; + + const keyword = dq.trim(); + if (!keyword) { + setSuggestions([]); + return; + } + + let alive = true; + (async () => { + try { + const nextSuggestions = await fetchPostcodeSuggestions(keyword); + if (!alive) return; + setSuggestions(nextSuggestions); + } catch { + if (!alive) return; + setSuggestions([]); + } + })(); + + return () => { + alive = false; + }; + }, [dq, open]); + + useEffect(() => { + if (!open) return; + + const keyword = dq.trim(); + + if (!keyword) { + setItems([]); + setTotal(0); + setLoading(false); + setSearchMessage(""); + return; + } + + const parsed = parsePostcodeInput(keyword, 1); + if (!parsed.ok) { + setItems([]); + setTotal(0); + setLoading(false); + setSearchMessage(parsed.message); + return; + } + + let alive = true; + + (async () => { + setLoading(true); + setSearchMessage(""); + try { + const result: PostcodeSearchResult = await fetchPostcode(parsed.value); + if (!alive) return; + + setItems(result.items); + setTotal(result.totalCount); + } catch { + if (!alive) return; + setItems([]); + setTotal(0); + setSearchMessage("우편번호 검색 중 오류가 발생했습니다."); + } finally { + if (alive) { + setLoading(false); + setSuggestions([]); + } + } + })(); + + return () => { + alive = false; + }; + }, [dq, open]); + + const showSuggest = open && q.trim().length >= 1 && suggestions.length > 0; + + if (!mounted || !open) return null; + + return createPortal( +
+ +
+ +
+
+ setQ(e.target.value)} + placeholder="예) 테헤란로 152-1" + className={[ + "w-full rounded-md border px-4 py-3 pr-12 text-[14px] outline-none", + "border-slate-300 focus:border-sky-500 focus:ring-2 focus:ring-sky-200", + ].join(" ")} + /> + + + + {/* suggestions dropdown */} + {showSuggest && ( +
+ {suggestions.map((s) => ( + + ))} +
+ )} +
+
+ +
+
+ {q.trim() + ? loading + ? "검색 중..." + : searchMessage || `총 ${total.toLocaleString()}개의 검색결과` + : "도로명과 건물번호를 입력하세요. 예) 테헤란로 152-1"} +
+ +
+ {!loading && q.trim() && items.length === 0 && !searchMessage && ( +
+ 검색 결과가 없습니다. +
+ )} + +
+ {items.map((it) => ( + + ))} +
+
+
+
+
+
, + document.body, + ); +} diff --git a/apps/dm_new/components/SubPageHeader.tsx b/apps/dm_new/components/SubPageHeader.tsx new file mode 100644 index 000000000..b18ee28a6 --- /dev/null +++ b/apps/dm_new/components/SubPageHeader.tsx @@ -0,0 +1,64 @@ +"use client"; + +import Link from "next/link"; + +export type Crumb = { label: string; href?: string }; + +type Props = { + title: string; + crumbs: Crumb[]; + note?: string; + underlineWidthClass?: string; + className?: string; + wrapClassName?: string; + hideFooterLine?: boolean; + hideGuideText?: boolean; +}; + +export default function SubPageHeader({ + title, + crumbs, + note = "* 는 필수 값입니다.", + // underlineWidthClass = "w-40", + className = "", + wrapClassName = "", + // hideFooterLine = false, + hideGuideText = false, +}: Props) { + return ( +
+
+
+ {/* 좌측 타이틀 */} +
+

{title}

+
+
+ + {/* 우측 브레드크럼 */} + +
+ {/* 하단 라인 + 안내문 */} + {!hideGuideText && ( +
+ * + {note.replace("*", "")} +
+ )} +
+
+ ); +} diff --git a/apps/dm_new/components/TopNav.tsx b/apps/dm_new/components/TopNav.tsx new file mode 100644 index 000000000..9a182560b --- /dev/null +++ b/apps/dm_new/components/TopNav.tsx @@ -0,0 +1,210 @@ +"use client"; + +import Link from "next/link"; +import Image from "next/image"; +import { usePathname, useRouter } from "next/navigation"; +import { useEffect, useMemo, useState } from "react"; +import LoginModal from "@/components/LoginModal"; +import { fetchAnonymousMenu, isMenuActive, type MenuItem } from "@/app/store/common"; +import { useDmStore } from "@/app/store"; + +// const LS_TOKEN_KEY = "reuse_accessToken"; +const LS_USER_KEY = "user"; + +const DEFAULT_NAV_ITEMS: Pick[] = [ + { menuCd: 1, menuKornNm: "물 재이용 소개", urlPath: "/reuse/intro/law", newWndwYn: "N" }, + { menuCd: 2, menuKornNm: "물 재이용 현황", urlPath: "/reuse/cases/rain", newWndwYn: "N" }, + { menuCd: 3, menuKornNm: "물 재이용 사례", urlPath: "/reuse/business/rain/suwon", newWndwYn: "N" }, + { menuCd: 4, menuKornNm: "수요신청", urlPath: "/reuse/apply/place", newWndwYn: "N" }, +]; + +export default function TopNav() { + const router = useRouter(); + const pathname = usePathname(); + + const { actionLogout } = useDmStore((state) => state); + const [loginOpen, setLoginOpen] = useState(false); + const [userName, setUserName] = useState(null); + const [topMenus, setTopMenus] = useState([]); + + useEffect(() => { + const sessionUser = sessionStorage.getItem(LS_USER_KEY); + if (sessionUser) { + const parsed = JSON.parse(sessionUser) as { dmdCoNm?: string }; + setUserName(parsed.dmdCoNm ?? "수요처"); + } else { + setUserName(null); + } + }, []); + + useEffect(() => { + let isMounted = true; + + (async () => { + try { + const loaded = await fetchAnonymousMenu(); + if (!isMounted) return; + + const level1 = loaded.filter((menu) => menu.levelNo === 1 || menu.parentId === null); + setTopMenus(level1); + } catch (error) { + console.error("Failed to load DN top menu", error); + if (!isMounted) return; + setTopMenus([]); + } + })(); + + return () => { + isMounted = false; + }; + }, []); + + const navItems = useMemo(() => { + if (topMenus.length > 0) return topMenus; + + return DEFAULT_NAV_ITEMS.map((item) => ({ + ...item, + parentId: null, + sortSeq: 0, + children: [], + levelNo: 1, + useYn: "Y", + otptYn: "Y", + })); + }, [topMenus]); + + const openLogin = () => setLoginOpen(true); + + const logout = async () => { + // localStorage.removeItem(LS_TOKEN_KEY); + // localStorage.removeItem(LS_USER_KEY); + setUserName(null); + await actionLogout(); + router.push("/main"); + }; + + return ( + <> +
+ {/* ✅ 좌/중/우 정렬이 되도록 컨테이너 구성 */} +
+
+ {/* 좌측: 로고 */} +
+ + HASUON + +
+ + {/* 중앙: 상단 메뉴 */} + + + {/* ✅ 우측: 로그인 버튼 / 로그인 정보 + 로그아웃 */} +
+ {!userName ? ( + + ) : ( + <> + + {userName}님 환영합니다. + + + + )} +
+
+
+
+ + setLoginOpen(false)} + onSuccess={({ userName: nextUserName }) => { + // localStorage.setItem(LS_USER_KEY, nextUserName); + // if (accessToken) localStorage.setItem(LS_TOKEN_KEY, accessToken); + setUserName(nextUserName); + setLoginOpen(false); + router.refresh(); + }} + /> + + ); +} diff --git a/apps/dm_new/components/ui/api/index.ts b/apps/dm_new/components/ui/api/index.ts new file mode 100644 index 000000000..abb0c2c73 --- /dev/null +++ b/apps/dm_new/components/ui/api/index.ts @@ -0,0 +1,99 @@ +//8880 +import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; +import { getCookie } from "@common/components/lib/cookie"; + + +const tokenId = getCookie(AUTH_USER_ID); +const Authorization = getCookie(CLAIM_NAME); + +export const fetchType = { + COMMON: "common", + WR: "wr", + FO: "fo", + SN: "sn" +}; + +const API_VERSION = "/api/v1" +const servers = { + common: "", + local: "", + fo: "/operation-service" + API_VERSION, + wr: "/reuse-service" + API_VERSION, + sn: "/sewerage-service" + API_VERSION, +} + +interface RequestParams { + [key: string]: unknown; +} + +export const getFetch = async (type: string, url: string) => { + const baseUrl = servers[type]; + const response = await fetch(baseUrl + url, { + method: "GET", + headers: { + accept: "application/json", + [AUTH_USER_ID]: tokenId ?? "", + [CLAIM_NAME]: Authorization ?? "", + }, + }); + + if (!response.ok) { + throw new Error(`오류 발생: ${response.status}`); + } + const res = await response.json(); + return res; +}; + +export const postFetch = async (url: string, params: RequestParams) => { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { + method: "POST", + headers: { + "Content-Type": "application/json", + [AUTH_USER_ID]: tokenId ?? "", + [CLAIM_NAME]: Authorization ?? "", + }, + body: JSON.stringify(params), + }); + + if (!response.ok) { + throw new Error(`오류 발생: ${response.status}`); + } + return response; +}; + +export const putFetch = async (url: string, params: RequestParams) => { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + [AUTH_USER_ID]: tokenId ?? "", + [CLAIM_NAME]: Authorization ?? "", + }, + body: JSON.stringify(params), + }); + + if (!response.ok) { + throw new Error(`오류 발생: ${response.status}`); + } + return response; +}; + +export const deleteFetch = async (url: string, params: RequestParams) => { + const baseUrl = servers[fetchType.WR]; + const response = await fetch(baseUrl + url, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + [AUTH_USER_ID]: tokenId ?? "", + [CLAIM_NAME]: Authorization ?? "", + }, + body: JSON.stringify(params), + }); + + if (!response.ok) { + throw new Error(`오류 발생: ${response.status}`); + } + return response; +}; \ No newline at end of file diff --git a/apps/dm_new/components/ui/common/searhDemandInput/component.tsx b/apps/dm_new/components/ui/common/searhDemandInput/component.tsx new file mode 100644 index 000000000..70ccf9efc --- /dev/null +++ b/apps/dm_new/components/ui/common/searhDemandInput/component.tsx @@ -0,0 +1,103 @@ +import { useDmStore } from '@/app/store'; +import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; + +type thisProps = { + label: string | undefined; + initValue: string | undefined; + setValue : (dmdCoNm?: string) => void; + setRtnData : (rtnData: DemandCompType[]) => void; +} + +export default function SearchInputText({label=undefined, initValue=undefined, setValue=()=>{}, setRtnData=()=>{}} :thisProps) { + + const { getDemandCompList, Sdm } = useDmStore((state) => state); + + const { + demandCompList + } = Sdm; + + const [isOpen, setIsOpen] = useState(false); + const [textValue, setTextValue] = useState(initValue || ''); + const wrapperRef = useRef(null); + const handleInputChange = useCallback((e: React.ChangeEvent) => { + const value = e.target.value; + setTextValue(value); + setValue(value); + if (value) { + setIsOpen(true); + getDemandCompList(value, 20); + } else { + setIsOpen(false); + } + }, [getDemandCompList, setValue]); + + const handleSuggestionClick = useCallback((dmdCoNm: string) => { + setTextValue(dmdCoNm); + setValue(dmdCoNm); + setIsOpen(false); + setRtnData(demandCompList); + }, [demandCompList, setRtnData, setValue]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
+ setIsOpen(true)} */ + className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder={`${label}을(를) 입력하세요`} + /> + {isOpen && ( +
+
+ {demandCompList && demandCompList.length > 0 ? demandCompList.map((item : DemandCompType, index : number) => ( +
handleSuggestionClick(item.dmdCoNm)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + handleSuggestionClick(item.dmdCoNm); + } + }} + > + {item.dmdInfoId.substring(10,14) !== '0000' ? ( + <> + 사업자번호 : {item.dmdInfoId.substring(0,10)}  + 종사업자번호 : {item.dmdInfoId.substring(10,14)}  + + ) : ( + <> + 사업자번호 : {item.dmdInfoId.substring(0,10)}  + + )} + {item.dmdCoNm} +
+ )) : ( +
+ 검색 결과가 없습니다. +
+ )} +
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/apps/dm_new/components/ui/common/utils/utils.ts b/apps/dm_new/components/ui/common/utils/utils.ts new file mode 100644 index 000000000..19c92bff2 --- /dev/null +++ b/apps/dm_new/components/ui/common/utils/utils.ts @@ -0,0 +1,54 @@ +export const sanitizeInput = (val: string, allowDecimal = false) => { + if (val === "") return ""; // 빈 값 허용 + + // 소숫점 허용 + if (allowDecimal) { + // 숫자와 점만 허용 + let cleaned = val.replace(/[^0-9.]/g, ""); + // 소숫점 여러 개일 경우 첫 번째만 유지 + const parts = cleaned.split("."); + if (parts.length > 2) { + cleaned = parts[0] + "." + parts.slice(1).join(""); + } + return cleaned; + } + + // 정수만 허용 + return val.replace(/[^0-9]/g, ""); +}; + + + +// utils: format Date <-> "YYYY-MM-DD" without moment +export const formatDateToYMD=(value: Date): string => { + if (!value) return ""; + // value can be Date | string ("YYYY-MM-DD") + if (typeof value === "string") { + // assume already in yyyy-mm-dd + if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value; + const d = new Date(value); + if (isNaN(d.getTime())) return ""; + return toYMD(d); + } + if (value instanceof Date) { + if (isNaN(value.getTime())) return ""; + return toYMD(value); + } + return ""; +} + +const toYMD=(date: Date): string => { + // avoid timezone shift by normalizing to local date parts + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} + +// const parseYMDToDate=(ymd: string): Date | null => { +// if (!/^\d{4}-\d{2}-\d{2}$/.test(ymd)) return null; +// const [y, m, d] = ymd.split("-").map(Number); +// const date = new Date(y, (m - 1), d); +// // validate round-trip +// return (date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d) ? date : null; +// } \ No newline at end of file diff --git a/apps/dm_new/components/ui/config/index.ts b/apps/dm_new/components/ui/config/index.ts new file mode 100644 index 000000000..1585bb767 --- /dev/null +++ b/apps/dm_new/components/ui/config/index.ts @@ -0,0 +1,6 @@ +import { PROXY_HOST } from "@/src/shared/config/env"; + +export const LOCAL_URL = PROXY_HOST; +export const DEV_URL_FO = "/operation-service"; +export const DEV_URL_WR = "/reuse-service"; +export const DEV_URL_SN = "/sewerage-service"; diff --git a/apps/dm_new/components/ui/map/SearchInputText/SearchInputText.tsx b/apps/dm_new/components/ui/map/SearchInputText/SearchInputText.tsx new file mode 100644 index 000000000..05f41d09b --- /dev/null +++ b/apps/dm_new/components/ui/map/SearchInputText/SearchInputText.tsx @@ -0,0 +1,139 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; + +type SearchType = { + bscFctNm: string; + bscFctCd: string; +}; + +type thisProps = { + label: string | undefined; + initValue: string | undefined; + setValue: (searchNm?: string) => void; + setRtnData: (rtnData: any) => void; + searchList?: SearchType[]; +}; + +export default function SearchInputText({ + label = undefined, + initValue = undefined, + setValue = () => {}, + setRtnData = () => {}, + searchList = [], +}: thisProps) { + const [isOpen, setIsOpen] = useState(false); + const [textValue, setTextValue] = useState(initValue || ""); + const [highlightIndex, setHighlightIndex] = useState(-1); + + const wrapperRef = useRef(null); + + const handleInputChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setTextValue(value); + setValue(value); + setHighlightIndex(-1); + setIsOpen(value !== ""); + }, + [setValue] + ); + + const handleSuggestionClick = useCallback( + (searchNm: string) => { + setValue(searchNm); + setTextValue(searchNm); + setIsOpen(false); + setRtnData(searchList?.find((item) => item.bscFctNm === searchNm) || {}); + }, + [searchList, setRtnData, setValue] + ); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (!isOpen || searchList.length === 0) return; + + if (e.key === "ArrowDown") { + e.preventDefault(); + setHighlightIndex((prev) => (prev + 1) % searchList.length); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setHighlightIndex((prev) => + prev <= 0 ? searchList.length - 1 : prev - 1 + ); + } else if (e.key === "Enter") { + e.preventDefault(); + if (searchList.length === 1 && searchList[0]) { + handleSuggestionClick(searchList[0].bscFctNm); + } else if (highlightIndex >= 0 && highlightIndex < searchList.length && searchList[highlightIndex]) { + handleSuggestionClick(searchList[highlightIndex].bscFctNm); + } + } + }, + [highlightIndex, isOpen, searchList, handleSuggestionClick] + ); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + wrapperRef.current && + !wrapperRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+ { + setTextValue(""); + setValue(""); + setIsOpen(false); + }} + /> + {isOpen && ( +
+
+ {searchList.length > 0 ? ( + searchList.map((item, index) => ( +
handleSuggestionClick(item.bscFctNm)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + handleSuggestionClick(item.bscFctNm); + } + }} + > + {item.bscFctNm} +
+ )) + ) : ( +
+ 검색 결과가 없습니다. +
+ )} +
+
+ )} +
+ ); +} diff --git a/apps/dm_new/components/ui/map/searchBar/searchBar.tsx b/apps/dm_new/components/ui/map/searchBar/searchBar.tsx new file mode 100644 index 000000000..7ca64bbce --- /dev/null +++ b/apps/dm_new/components/ui/map/searchBar/searchBar.tsx @@ -0,0 +1,118 @@ +"use client"; +import { useDmStore } from "@/app/store"; +import { + SelectBox, + type SelectDataType, +} from "@common/business_components/ui"; +import { Button, Label } from "@common/components/ui"; +import { useCallback, useEffect, useState } from "react"; +import SearchInputText from "../SearchInputText/SearchInputText"; + +export function SearchBar({ + setHasuPk, + onApply, +}: { + setHasuPk: (hasuPk: any) => void; + onApply: () => void; +}) { + const { map, Common, initializeCommonData, findRuseFclt } = useDmStore( + (state) => state + ); + const [sidoValue, setSidoValue] = useState(""); + const [sigunValue, setSigunValue] = useState(""); + const [filteredSigunData, setFilteredSigunData] = useState([ + { text: "전체", val: "0" }, + ]); + + useEffect(() => { + initializeCommonData(); + }, [initializeCommonData]); + + const initializeSigunData = useCallback( + (sidoCd: string) => { + if (sidoCd === "0") { + setSigunValue("0"); + setFilteredSigunData([{ text: "전체", val: "0" }]); + } else { + const newFilteredData = [ + { text: "전체", val: "0" }, + ...Common.selectSigunData.filter( + (item: SelectDataType) => item.val.substring(0, 2) === sidoCd + ), + ]; + setSigunValue("0"); + setFilteredSigunData(newFilteredData); + } + }, + [Common.selectSigunData] + ); + + const onSelectValue = useCallback( + (val: string, type: string) => { + switch (type) { + case "ctpvCd": + initializeSigunData(val); + setSidoValue(val); + break; + case "sggCd": + setSigunValue(val); + } + }, + [initializeSigunData] + ); + + return ( +
+
+ onSelectValue(val.toString(), "ctpvCd")} + value={sidoValue || Common.selectSidoData[0]?.val || ""} + /> + onSelectValue(val.toString(), "sggCd")} + value={sigunValue} + /> + + { + let sggOrCity = ""; + if (searchNm) { + if (sigunValue === "0" || sigunValue === "") { + sggOrCity = sidoValue; + } else { + sggOrCity = sigunValue; + } + findRuseFclt(sggOrCity, searchNm); + } + }} + setRtnData={(rtnData: any) => { + setHasuPk(rtnData); + }} + searchList={map.ruseFcltList || []} + /> + +
+
+ ); +} diff --git a/apps/dm_new/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx b/apps/dm_new/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx new file mode 100644 index 000000000..c37270df8 --- /dev/null +++ b/apps/dm_new/components/ui/sdmInfoInsertForm/AccessibleFormField.tsx @@ -0,0 +1,35 @@ +import { FormUi } from "@common/business_components/ui"; +import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form"; +import { cloneElement, isValidElement } from "react"; +interface AccessibleFormFieldProps { + field: ControllerRenderProps>; + label: string; + required?: boolean; + error?: FieldError; + isDisabled?: boolean; + inputComponent: React.ReactElement<{ disabled?: boolean }>; +} + +export function AccessibleFormField({ + label, + required = false, + inputComponent, + isDisabled = false, +}: AccessibleFormFieldProps) { + const labelText = required ? `${label} *` : label; + + return ( + +
+ {isValidElement(inputComponent) + ? cloneElement(inputComponent, { disabled: isDisabled } as Partial) + : inputComponent} + {/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => ( +

+ {msg} +

+ ))} */} +
+
+ ); +} diff --git a/apps/dm_new/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm_new/components/ui/sdmInfoInsertForm/component.tsx new file mode 100644 index 000000000..44c6d3f82 --- /dev/null +++ b/apps/dm_new/components/ui/sdmInfoInsertForm/component.tsx @@ -0,0 +1,962 @@ +// import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; +// import { useDmStore } from "@/app/store"; +// import { +// CANDIDATE, +// // DatePicker, +// FormField, +// FormLabel, +// FormUi, +// SelectBox, +// UiModal, +// } from "@common/business_components"; +// import { Input } from "@common/components"; +// import { Button, Label } from "@common/components/ui"; +// import { useSearchParams } from "next/navigation"; +// import { useCallback, useEffect, useState } from "react"; +// import { AccessibleFormField } from "./AccessibleFormField"; +// import { +// type ControllerRenderProps, +// type UseFormReturn, +// type FieldError, +// useWatch, +// } from "react-hook-form"; +// import SearchInputText from "../common/searhDemandInput/component"; +// import React from "react"; +// import { formatDateToYMD, sanitizeInput } from "../common/utils/utils"; +// import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +// import type { SdmInfoFormData } from "@/app/store/sdmMng"; + +// export interface SdmInfoRegFormProps { +// form: UseFormReturn; +// onCheckboxChange?: (checked: boolean) => void; +// facilityName?: string; +// } + +// type FieldComponent = ( +// field: ControllerRenderProps +// ) => React.ReactElement<{ disabled?: boolean }>; + +// export function SdmInfoRegForm({ +// form, +// onCheckboxChange, +// facilityName, +// }: SdmInfoRegFormProps) { +// const { initializeCommonData, Sdm } = useDmStore((state) => state); +// const { useTypesData, defaultRules, disableRules } = Sdm; +// const searchParams = useSearchParams(); + +// const [sdmNmValue, setSdmNmValue] = useState( +// searchParams.get("dmdCoNm") || "" +// ); + +// const [frontPHdata, setFrontPHdata] = useState("0"); +// const [backPHdata, setBackPHdata] = useState("0"); +// const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false); +// const [isSimilarCase, setIsSimilarCase] = useState(false); +// const [key, setKey] = useState(0); +// // 후보지 팝업 오픈 상태 + +// //모달 조회에 필요한 데이터 +// const [sply, setSply] = useState(0); +// const [usageCd, setUsageCd] = useState("청소"); +// const [money, setMoney] = useState(0); + +// useEffect(() => { +// initializeCommonData(); +// }, [initializeCommonData]); + +// useEffect(() => { +// console.log("candidateTitle ", form.getValues("candidateTitle")); +// }, [form.getValues]); + +// useEffect(() => { +// if (useTypesData && useTypesData.length > 0) { +// setTimeout(() => { +// form.setValue("usePurpose", useTypesData[0]?.val?.toString() || ""); +// }, 500); +// } +// }, [useTypesData, form.setValue, form]); + +// useEffect(() => { +// if (facilityName) { +// setSdmNmValue(facilityName); +// form.setValue("sdmNm", facilityName); +// setKey((prev) => prev + 1); +// } +// }, [facilityName, form.setValue, form]); + +// useEffect(() => { +// if (searchParams.get("dmdCoNm")) { +// form.setValue("sdmNm", searchParams.get("dmdCoNm") || ""); +// setSdmNmValue(searchParams.get("dmdCoNm") || ""); +// setKey((prev) => prev + 1); +// } +// }, [searchParams, form.setValue, form]); + +// const searchCandidate = (e: React.MouseEvent) => { +// e.preventDefault(); +// e.stopPropagation(); +// const dmdInfoId = JSON.parse( +// sessionStorage.getItem("user") || "{}" +// )?.dmdInfoId?.toString(); + +// const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; +// const width = window.screen.availWidth; +// const height = window.screen.availHeight; +// const left = 0; +// const top = 0; +// const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`; +// window.open(url, "candidatePopup", popupOptions); +// }; + +// const searchSimilarCase = (e: React.MouseEvent) => { +// e.preventDefault(); +// e.stopPropagation(); +// setIsSimilarCase(true); +// }; + +// const handleCheckboxChange = (checked: boolean) => { +// setIsBznmTotNoDisabled(checked); +// onCheckboxChange?.(checked); +// }; + +// useEffect(() => { +// const watchPH = form.getValues(`ph`); +// if (watchPH && watchPH !== undefined) { +// const [frontPH, backPH] = watchPH.split("~"); +// if (frontPH) { +// setFrontPHdata(frontPH); +// } +// if (backPH) { +// setBackPHdata(backPH); +// } +// } else { +// setFrontPHdata("0"); +// setBackPHdata("0"); +// } +// }, [form.getValues(`ph`)]); + +// const isSetPh = useCallback( +// (typePHdata: string, index: number) => { +// const PHdata = form.getValues("ph"); +// const isPH = PHdata?.split("~")[index] || ""; +// if (index == 0) { +// form.setValue("ph", `${isPH}~${typePHdata}`); +// setBackPHdata(typePHdata); +// } else if (index == 1) { +// form.setValue("ph", `${typePHdata}~${isPH}`); +// setFrontPHdata(typePHdata); +// } +// }, +// [form] +// ); + +// const usePurpose = useWatch({ control: form.control, name: "usePurpose" }); + +// useEffect(() => { +// if (!usePurpose) return; + +// // Apply default values +// const defaults = defaultRules[usePurpose]; + +// if (defaults) { +// Object.entries(defaults).forEach(([field, value]) => { +// form.setValue(field as keyof SdmInfoFormData, value, { +// shouldDirty: true, +// }); +// }); +// } +// }, [form.getValues("usePurpose")]); + +// // 시설 선택시 이벤트 +// const selectComp = useCallback( +// (rtnData: DemandCompType[]) => { +// const rtnCompData = rtnData?.[0]; +// form.setValue("sdmNm", rtnCompData?.dmdCoNm || ""); +// }, +// [form] +// ); + +// const formFields: Array<{ +// name: keyof SdmInfoFormData; +// label: string; +// component: FieldComponent | undefined; +// formIndex: number; +// required?: boolean; +// }> = [ +// { +// name: "sdmNm", +// label: "수요처선택", +// formIndex: 0, +// required: true, +// component: (field) => ( +//
+// { +// setSdmNmValue(val || ""); +// field.onChange(val || ""); +// }} +// setRtnData={selectComp} +// initValue={facilityName || sdmNmValue} +// {...field} +// /> +//
+// ), +// }, +// { +// name: "demandYmd", +// label: "요구시기", +// formIndex: 0, +// required: true, +// component: (field) => ( +//
+// {/* { +// field.onChange(date as Date); +// }} +// className="" +// //defaultMonth={field.value as Date} +// field={{ +// value: field.value as Date, +// onChange: (date: Date | undefined) => { +// field.onChange(date as Date); +// }, +// }} +// showOutsideDays={true} +// selectClass={undefined} +// /> */} +// {/* 네이티브 input */} +// { +// const selectedDate = e.target.value ? new Date(e.target.value) : null; +// field.onChange(selectedDate); +// }} +// value={ formatDateToYMD(field.value as Date) } +// /> +//
+// ), +// }, +// { +// name: "demandQnty", +// label: "요구수량(㎥/d)", +// formIndex: 0, +// required: true, +// component: (field) => ( +// { +// field.onChange(e); +// setSply(Number(e.target.value)); +// }} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// ), +// }, +// { +// name: "usePurpose", +// label: "사용용도", +// formIndex: 0, +// required: true, +// component: (field) => ( +// { +// field.onChange(val.toString()); +// setUsageCd(val.toString()); +// }} +// className="" +// value={field.value as string} +// /> +// ), +// }, +// { +// name: "money", +// label: "희망단가(원)", +// formIndex: 0, +// required: true, +// component: (field) => ( +// +// { +// field.onChange(e); +// setMoney(Number(e.target.value)); +// }} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// ), +// }, +// { +// name: "reqWaterQlty", +// label: "요구수질", +// formIndex: 0, +// required: true, +// component: undefined, +// }, + +// { +// name: "candidateTitle", +// label: CANDIDATE || "공급처 후보지 선택(필수아님)", +// formIndex: 2, +// component: () => ( +//
+//
+// +// +//
+//
+// {(form.getValues("candidateTitle") || []).map( +// (candidate: any, index: number) => ( +// +// ) +// )} +//
+//
+// ), +// }, +// ]; + +// const normalFields = formFields.filter( +// (f) => f.formIndex === 0 && f.component +// ); + +// const candidateFields = formFields.filter( +// (f) => f.formIndex === 2 && f.component +// ); + +// return ( +// <> +// {normalFields.map(({ name, label, component, required }) => +// component ? ( +// +// name === "money" ? ( +//
+// +// {component(field)} +//
+// ) : ( +// // {component(field)} +// +// ) +// } +// /> +// ) : null +// )} + +//
+// 요구수질 +//
+// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "불검출" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// {/* TODO: 총대장균군수 확인 필요*/} +// {field.value !== null ? ( +// +// 이하 +// +// ) : ( +// <> +// )} +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const cleanValue = sanitizeInput( +// e.target.value, +// true +// ); +// field.onChange( +// cleanValue === "" ? null : Number(cleanValue) +// ); +// }} +// value={field.value ?? ""} +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이상 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const cleanValue = sanitizeInput( +// e.target.value, +// true +// ); +// field.onChange( +// cleanValue === "" ? null : Number(cleanValue) +// ); +// }} +// value={field.value ?? ""} +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +//
+// +//
+// { +// const cleanValue = sanitizeInput(e.target.value, true); +// isSetPh(cleanValue, 1); +// }} +// value={frontPHdata ?? ""} +// maxLength={5} +// /> +// ~ +// { +// const cleanValue = sanitizeInput(e.target.value, true); +// isSetPh(cleanValue, 0); +// }} +// value={backPHdata ?? ""} +// maxLength={5} +// /> +//
+//
+// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +// { +// field.onChange(val.toString()); +// }} +// value={field.value} +// readOnly={isDisabled} +// onBlur={field.onBlur} +// ref={field.ref} +// /> +// +// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// return ( +// +//
+//
+// { +// field.onChange(e.target.value); +// }} +// value={ +// field.value !== undefined && field.value !== "" +// ? `${field.value}` +// : "" +// } +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +//
+//
+//
+// ); +// }} +// /> +//
+//
+ +// {candidateFields.map(({ name, label, component, required }, index) => +// component ? ( +// ( +// // +// // {component(field)} +// // +// +// )} +// /> +// ) : null +// )} + +// {isSimilarCase && ( +// +// } +// bodyClass="sm:max-w-[100vw] w-[1400px] h-[100dvh] max-h-[900px] overflow-y-auto overflow-x-hidden" +// open={isSimilarCase} +// onOpenChange={(open) => !open && setIsSimilarCase(false)} +// noHeaderYn="Y" +// noFooterYn="Y" +// /> +// )} +// +// ); +// } diff --git a/apps/dm_new/components/ui/sdmLoginForm/component.tsx b/apps/dm_new/components/ui/sdmLoginForm/component.tsx new file mode 100644 index 000000000..7ec417c71 --- /dev/null +++ b/apps/dm_new/components/ui/sdmLoginForm/component.tsx @@ -0,0 +1,197 @@ +import { useDmStore } from "@/app/store"; +import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import { Button, Input } from "@common/components"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { sha512 } from 'js-sha512'; +import React, { useEffect, useMemo, useState } from "react"; +import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form"; +import toast from "react-hot-toast"; +import * as z from "zod"; + +export function SdmLoginForm() { + const { actionLogin, initializeCommonData } = useDmStore((state) => state); + const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true); + + const formatBusinessNumber = (value: string) => { + // 숫자만 추출 + const digitsOnly = value.replace(/\D/g, ""); + + // 10자리까지만 처리 + const limitedDigits = digitsOnly.slice(0, 10); + + // 3-2-5 형식으로 포맷팅 + if (limitedDigits.length <= 3) { + return limitedDigits; + } else if (limitedDigits.length <= 5) { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3)}`; + } else { + return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; + } + }; + + // 스키마를 useMemo로 최적화 + const sdmLoginFormSchema = useMemo(() => z.object({ + dmdInfoId: z.string() + .min(1, "사업자등록번호를 입력해주세요") + .regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"), + bznmTotNo: isBznmTotNoDisabled + ? z.string().optional() + : z.string().length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"), + password: z.string() + .min(1, "비밀번호를 입력해주세요") + .min(8, "비밀번호는 8자 이상 입력해주세요") + .max(16, "비밀번호는 16자 이내로 입력해주세요"), + rgtr: z.string().optional(), + dmdAplyPswd: z.string().optional(), + }), [isBznmTotNoDisabled]); + + type SdmLoginForm = z.infer; + type FieldComponent = (field: ControllerRenderProps) => React.ReactElement; + + const formSchema = sdmLoginFormSchema; + + useEffect(() => { + initializeCommonData(); + }, [initializeCommonData]); + + const defaultValues: SdmLoginForm = { + dmdInfoId: '', + bznmTotNo: '', + password: '', + rgtr: '', + dmdAplyPswd: '', + }; + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues, + mode: "onChange", + }); + + const handleCheckboxChange = (checked: boolean) => { + setIsBznmTotNoDisabled(checked); + }; + + const initPassword = () => { + toast( + (t) => ( +
+
+ 관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다. +
+ +
+ ), + { + id: "pwd-reset", + duration: Infinity, + position: "top-center", + } + ); + } + + const handleLogin = () => { + const data = form.getValues(); + const passwordStr = sha512.hex(data.password) + data.dmdAplyPswd = passwordStr; + const param = { + dmdInfoId: data.dmdInfoId + (isBznmTotNoDisabled ? '0000' : data.bznmTotNo), + dmdAplyPswd: data.dmdAplyPswd, + } + actionLogin(param as unknown as DemandCompType); + }; + + const formFields: Array<{ + name: keyof SdmLoginForm; + label: string; + component: FieldComponent | undefined; + }> = [ + { + name: "dmdInfoId", + label: "사업자등록번호", + component: (field) => ( + { + const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); + field.onChange(dmdId); + }} + /> + ) + }, + { + name: "bznmTotNo", + label: BZNM_TOT_NO || "종사업자등록번호", + component: (field) => ( + + + + ) + }, + { + name: "password", + label: "비밀번호", + component: (field) => ( + + ) + } + ]; + + return ( + <> +
+ + {formFields.map(({ name, label, component }) => ( + ( + label === '종사업자등록번호' ? + <> + {component && component(field)} + + : + {component && component(field)} + + )} + /> + ))} + +
+
+ +
비밀번호 초기화 요청
+
+
+
+ + ); +} \ No newline at end of file diff --git a/apps/dm_new/eslint.config.mjs b/apps/dm_new/eslint.config.mjs new file mode 100644 index 000000000..7de132089 --- /dev/null +++ b/apps/dm_new/eslint.config.mjs @@ -0,0 +1,32 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const isProd = process.env.NODE_ENV === "production"; + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + files: ["**/*.{ts,tsx,mts,cts}"], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + }, + }, + {rules:{ + // other rules.. + "@typescript-eslint/no-explicit-any": "off", + // "@next/next/no-img-element": "off", + ...(isProd ? { "react-hooks/exhaustive-deps": "off" } : {}), + }}, +]; + +export default eslintConfig; diff --git a/apps/dm_new/middleware.ts b/apps/dm_new/middleware.ts new file mode 100644 index 000000000..e7d7a1509 --- /dev/null +++ b/apps/dm_new/middleware.ts @@ -0,0 +1,179 @@ +import { NextRequest, NextResponse } from "next/server"; +import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config"; +// import { SERVER_API_FO_URL, SERVER_API_WR_URL } from "./src/shared/config/env"; + +// const CLAIM_NAME = "Authorization"; +// const AUTH_USER_ID = "token-id"; +const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_URL; +const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; +const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; +const GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL || ""; +const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || ""; +const AdressKey = process.env.NEXT_PRIVATE_WR_ADDRESS_KEY || ""; + +/** 공통: URLSearchParams의 값을 decodeURIComponent 적용하여 querystring 생성 */ +function toDecodedQueryString(params: URLSearchParams): string { + const entries: string[][] = Array.from(params.entries()).map(([k, v]) => { + try { + return [k, decodeURIComponent(v)] as string[]; + } catch { + return [k, v] as string[]; + } + }); + return new URLSearchParams(entries).toString(); +} + +export function middleware(req: NextRequest) { + const pathname = req.nextUrl.pathname; + console.log(`[Middleware] Request URL: ${req.url}`); + const searchParams = req.nextUrl.searchParams; + + // if (pathname.startsWith("/server")) { + // const queryParams = searchParams.toString(); + // const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ + // queryParams ? `?${queryParams}` : "" + // }`; + // console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`); + // return NextResponse.rewrite(new URL(newUrl)); + // } + + if (pathname.startsWith("/server")) { + const decodedParams = Array.from(searchParams.entries()) + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); + + const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${decodedParams ? `?${decodedParams}` : "" + }`; + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname === "/") { + console.log(" maim 화면이동 "); + return NextResponse.redirect(new URL("/main", req.url)); + } + + if (pathname.startsWith("/attach-service")) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + const newUrl = `${serverApiUrl}${pathname}`; + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + console.log(`[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + + if (pathname.startsWith("/user-service") + || pathname.startsWith("/cms-service")) { + let queryParams = searchParams.toString(); + const requestHeaders = new Headers(req.headers); + const newUrl = `${serverApiUrl}${pathname}${queryParams ? `?${queryParams}` : ""}`; + + console.log(`[Middleware] Rewriting from '/user-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } + + if (pathname.startsWith("/geoserver")) { + let queryParams = searchParams.toString(); + const typename = searchParams.get("TYPENAME"); + console.log("typename ", typename); + + let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + + if ( + typename == "hasudo:tb_il_swage_prcs_fclt" || //하수처리시설 + typename == "hasudo:tb_ms_swage_prcs_zone" || //하수처리구역 + typename == "hasudo:tb_ms_sbmrg_dngr_map" || //침수위험지도 + typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도 + ) { + newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + } else if ( + typename == "hasudo:tb_ms_swage_ppl" || //관로 + typename == "hasudo:tb_ms_swage_mhl" || //맨홀 + typename == "hasudo:tb_ms_swage_pmstn" || //펌프장 + typename == "hasudo:tb_ms_stofcbr" //우수토실 + ) { + newUrl = `${LX_GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; + } else if ( + typename == "hasudo:om_dt_untrt_swg_rain" || //중점우수토실 + typename == "hasudo:om_wqlt_station" || //관측소 + typename == "hasudo:tb_wr_bdst_ruse_fclt" || //재이용시설 + typename == "hasudo:tb_wr_rnwtr_utztn_fclt" || //빗물이용시설 + typename == "hasudo:tb_wr_rswtr_fclt" || //중수도시설 + typename == "hasudo:tb_wr_htdfl_fclt" || //온배수시설 + typename == "hasudo:tb_wr_ruse_fclt_dmd_co" || //수요처 + typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 + ) { + const url = req.nextUrl.clone(); + url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); + queryParams = url.searchParams.toString(); + newUrl = `${GATEWAY_URL}/api/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; + } + + console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/reuse-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; + console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } else if (pathname.startsWith("/operation-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; + console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } else if (pathname.startsWith("/sewerage-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${GATEWAY_URL}${pathname}${queryParams ? `?${queryParams}` : ""}`; + console.log(`[Middleware] Rewriting from '/sewerage-service' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/ngii-server")) { + const decodedQuery = toDecodedQueryString(searchParams); + const newUrl = `${process.env.NEXT_PUBLIC_NGII_URL}${pathname.replace("/ngii-server", "")}${decodedQuery ? `?${decodedQuery}` : ""}`; + console.log(`[Middleware] Rewriting from '${pathname}' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + if (pathname.startsWith("/bussiness-juso-service")) { + console.log("AdressKey ", AdressKey); + const params = new URLSearchParams({ + currentPage: "1", // 원하는 페이지 + countPerPage: "5", // 페이지당 결과 수 + keyword: searchParams.get("keyword") || "", // 검색어(주소) + confmKey: AdressKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯 + resultType: "json", // 결과 형식 기본은 xml + }); + const newUrl = `${BUSINESS_JUSO_API_URL}?${params.toString()}`; + console.log(`[Middleware] Request to business.juso.go.kr`); + console.log(req.url); + console.log(`[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}`); + return NextResponse.rewrite(new URL(newUrl)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: [ + "/server/:path*", + "/user-service/:path*", + "/cms-service/:path*", + "/attach-service/:path*", + "/geoserver/:path*", + "/operation-service/:path*", + "/reuse-service/:path*", + "/sewerage-service/:path*", + "/bussiness-juso-service/:path*", + "/ngii-server/:path*", + "/", + ], +}; diff --git a/apps/dm_new/next-env.d.ts b/apps/dm_new/next-env.d.ts new file mode 100644 index 000000000..1b3be0840 --- /dev/null +++ b/apps/dm_new/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/dm_new/next.config.ts b/apps/dm_new/next.config.ts new file mode 100644 index 000000000..5b5bf85da --- /dev/null +++ b/apps/dm_new/next.config.ts @@ -0,0 +1,57 @@ +import type { NextConfig } from "next"; +// import path from "path"; + +const subDomain = + process.env.NEXT_PUBLIC_PROXY_HOST || "http://localhost:3000"; + +const nextConfig: NextConfig = { + output: "standalone", + experimental: { + proxyTimeout: 600000, + }, + + // // 🔥 반드시 필요 + // webpack(config) { + // config.resolve.alias = { + // ...(config.resolve.alias || {}), + // // monorepo 구조 기준 + // "@common": path.resolve(__dirname, "../../packages/common"), + // }; + // return config; + // }, + + async rewrites() { + return { + beforeFiles: [ + { + source: "/_next/static/:path*", + destination: `${subDomain}/_next/static/:path*`, + has: [ + { + type: "header", + key: "referer", + value: "(.*)/pst(.*)", + }, + ], + }, + ], + afterFiles: [ + { + source: "/sewerage-service/:path*", + destination: `${subDomain}/sewerage-service/:path*`, + }, + { + source: "/reuse-service/:path*", + destination: `${subDomain}/reuse-service/:path*`, + }, + { + source: "/operation-service/:path*", + destination: `${subDomain}/operation-service/:path*`, + }, + ], + fallback: [], + }; + }, +}; + +export default nextConfig; diff --git a/apps/dm_new/package.json b/apps/dm_new/package.json new file mode 100644 index 000000000..f318baf0b --- /dev/null +++ b/apps/dm_new/package.json @@ -0,0 +1,37 @@ +{ + "name": "dm_new", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev -p 3015", + "build": "next build", + "start:dm_new": "next start -p 3015", + "lint": "next lint" + }, + "dependencies": { + "@common/assets": "workspace:*", + "@common/business_components": "workspace:*", + "@common/components": "workspace:*", + "@hookform/resolvers": "^4.1.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "js-cookie": "^3.0.5", + "js-sha512": "^0.9.0", + "lucide-react": "^0.474.0", + "next": "15.3.8", + "react-hook-form": "^7.54.2", + "react-hot-toast": "^2.5.2", + "tailwind-merge": "^3.0.1", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.2" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/webpack": "^5.28.5", + "eslint": "^9", + "eslint-config-next": "15.1.7", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/apps/dm_new/postcss.config.mjs b/apps/dm_new/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/apps/dm_new/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/dm_new/public/favicon.ico b/apps/dm_new/public/favicon.ico new file mode 100644 index 000000000..d304b6981 Binary files /dev/null and b/apps/dm_new/public/favicon.ico differ diff --git a/apps/dm_new/public/fonts/NotoSansKR-Regular.woff2 b/apps/dm_new/public/fonts/NotoSansKR-Regular.woff2 new file mode 100644 index 000000000..61d064695 Binary files /dev/null and b/apps/dm_new/public/fonts/NotoSansKR-Regular.woff2 differ diff --git a/apps/dm_new/public/images/landing/australia_01.png b/apps/dm_new/public/images/landing/australia_01.png new file mode 100644 index 000000000..756e0cf60 Binary files /dev/null and b/apps/dm_new/public/images/landing/australia_01.png differ diff --git a/apps/dm_new/public/images/landing/business/globalCase/australia_01.png b/apps/dm_new/public/images/landing/business/globalCase/australia_01.png new file mode 100644 index 000000000..756e0cf60 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/globalCase/australia_01.png differ diff --git a/apps/dm_new/public/images/landing/business/globalCase/eu_01.png b/apps/dm_new/public/images/landing/business/globalCase/eu_01.png new file mode 100644 index 000000000..05de059c8 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/globalCase/eu_01.png differ diff --git a/apps/dm_new/public/images/landing/business/globalCase/overview_01.png b/apps/dm_new/public/images/landing/business/globalCase/overview_01.png new file mode 100644 index 000000000..14ae574ba Binary files /dev/null and b/apps/dm_new/public/images/landing/business/globalCase/overview_01.png differ diff --git a/apps/dm_new/public/images/landing/business/globalCase/singapore_01.png b/apps/dm_new/public/images/landing/business/globalCase/singapore_01.png new file mode 100644 index 000000000..78cea99fb Binary files /dev/null and b/apps/dm_new/public/images/landing/business/globalCase/singapore_01.png differ diff --git a/apps/dm_new/public/images/landing/business/globalCase/taiwan_01.png b/apps/dm_new/public/images/landing/business/globalCase/taiwan_01.png new file mode 100644 index 000000000..a972930ab Binary files /dev/null and b/apps/dm_new/public/images/landing/business/globalCase/taiwan_01.png differ diff --git a/apps/dm_new/public/images/landing/business/rain/rain_01.png b/apps/dm_new/public/images/landing/business/rain/rain_01.png new file mode 100644 index 000000000..39433ad8b Binary files /dev/null and b/apps/dm_new/public/images/landing/business/rain/rain_01.png differ diff --git a/apps/dm_new/public/images/landing/business/rain/rain_02.png b/apps/dm_new/public/images/landing/business/rain/rain_02.png new file mode 100644 index 000000000..fb0e6e107 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/rain/rain_02.png differ diff --git a/apps/dm_new/public/images/landing/business/rain/rain_03.png b/apps/dm_new/public/images/landing/business/rain/rain_03.png new file mode 100644 index 000000000..89cb44973 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/rain/rain_03.png differ diff --git a/apps/dm_new/public/images/landing/business/rain/rain_04.png b/apps/dm_new/public/images/landing/business/rain/rain_04.png new file mode 100644 index 000000000..61d38bb6a Binary files /dev/null and b/apps/dm_new/public/images/landing/business/rain/rain_04.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/bolyeong_01.png b/apps/dm_new/public/images/landing/business/sewage/bolyeong_01.png new file mode 100644 index 000000000..5fd31fbf4 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/bolyeong_01.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/cheongju_01.png b/apps/dm_new/public/images/landing/business/sewage/cheongju_01.png new file mode 100644 index 000000000..f1565fdc9 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/cheongju_01.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/guri_01.png b/apps/dm_new/public/images/landing/business/sewage/guri_01.png new file mode 100644 index 000000000..4e14c957b Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/guri_01.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/guri_02.png b/apps/dm_new/public/images/landing/business/sewage/guri_02.png new file mode 100644 index 000000000..9ca300ca4 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/guri_02.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_01.png b/apps/dm_new/public/images/landing/business/sewage/hwanggu_01.png new file mode 100644 index 000000000..91002a67c Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/hwanggu_01.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_02.png b/apps/dm_new/public/images/landing/business/sewage/hwanggu_02.png new file mode 100644 index 000000000..8485a9268 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/hwanggu_02.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_03.png b/apps/dm_new/public/images/landing/business/sewage/hwanggu_03.png new file mode 100644 index 000000000..ee2a28f65 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/hwanggu_03.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/paju_01.png b/apps/dm_new/public/images/landing/business/sewage/paju_01.png new file mode 100644 index 000000000..6cc8592db Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/paju_01.png differ diff --git a/apps/dm_new/public/images/landing/business/sewage/yeosu_01.png b/apps/dm_new/public/images/landing/business/sewage/yeosu_01.png new file mode 100644 index 000000000..bac801fa2 Binary files /dev/null and b/apps/dm_new/public/images/landing/business/sewage/yeosu_01.png differ diff --git a/apps/dm_new/public/images/landing/calendar.svg b/apps/dm_new/public/images/landing/calendar.svg new file mode 100644 index 000000000..471170672 --- /dev/null +++ b/apps/dm_new/public/images/landing/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dm_new/public/images/landing/card_01.jpg b/apps/dm_new/public/images/landing/card_01.jpg new file mode 100644 index 000000000..fea6746d6 Binary files /dev/null and b/apps/dm_new/public/images/landing/card_01.jpg differ diff --git a/apps/dm_new/public/images/landing/card_02.jpg b/apps/dm_new/public/images/landing/card_02.jpg new file mode 100644 index 000000000..7faab9476 Binary files /dev/null and b/apps/dm_new/public/images/landing/card_02.jpg differ diff --git a/apps/dm_new/public/images/landing/card_03.jpg b/apps/dm_new/public/images/landing/card_03.jpg new file mode 100644 index 000000000..8b23f81d1 Binary files /dev/null and b/apps/dm_new/public/images/landing/card_03.jpg differ diff --git a/apps/dm_new/public/images/landing/card_04.jpg b/apps/dm_new/public/images/landing/card_04.jpg new file mode 100644 index 000000000..4537143e9 Binary files /dev/null and b/apps/dm_new/public/images/landing/card_04.jpg differ diff --git a/apps/dm_new/public/images/landing/cases/greywater_building.png b/apps/dm_new/public/images/landing/cases/greywater_building.png new file mode 100644 index 000000000..151cab2b5 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/greywater_building.png differ diff --git a/apps/dm_new/public/images/landing/cases/greywater_factory.png b/apps/dm_new/public/images/landing/cases/greywater_factory.png new file mode 100644 index 000000000..617a73008 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/greywater_factory.png differ diff --git a/apps/dm_new/public/images/landing/cases/greywater_overall.png b/apps/dm_new/public/images/landing/cases/greywater_overall.png new file mode 100644 index 000000000..1bc03c5f4 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/greywater_overall.png differ diff --git a/apps/dm_new/public/images/landing/cases/greywater_usage.png b/apps/dm_new/public/images/landing/cases/greywater_usage.png new file mode 100644 index 000000000..729bbd023 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/greywater_usage.png differ diff --git a/apps/dm_new/public/images/landing/cases/rainwater_building.png b/apps/dm_new/public/images/landing/cases/rainwater_building.png new file mode 100644 index 000000000..6a9db8480 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/rainwater_building.png differ diff --git a/apps/dm_new/public/images/landing/cases/rainwater_usage.png b/apps/dm_new/public/images/landing/cases/rainwater_usage.png new file mode 100644 index 000000000..31403b8cc Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/rainwater_usage.png differ diff --git a/apps/dm_new/public/images/landing/cases/rainwater_usage_amount.png b/apps/dm_new/public/images/landing/cases/rainwater_usage_amount.png new file mode 100644 index 000000000..61078a038 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/rainwater_usage_amount.png differ diff --git a/apps/dm_new/public/images/landing/cases/treated_water_facility.png b/apps/dm_new/public/images/landing/cases/treated_water_facility.png new file mode 100644 index 000000000..280ffa116 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/treated_water_facility.png differ diff --git a/apps/dm_new/public/images/landing/cases/treated_water_region.png b/apps/dm_new/public/images/landing/cases/treated_water_region.png new file mode 100644 index 000000000..977df110b Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/treated_water_region.png differ diff --git a/apps/dm_new/public/images/landing/cases/treated_water_usage.png b/apps/dm_new/public/images/landing/cases/treated_water_usage.png new file mode 100644 index 000000000..a2f55cd40 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/treated_water_usage.png differ diff --git a/apps/dm_new/public/images/landing/cases/treated_water_year.png b/apps/dm_new/public/images/landing/cases/treated_water_year.png new file mode 100644 index 000000000..83a5e68b6 Binary files /dev/null and b/apps/dm_new/public/images/landing/cases/treated_water_year.png differ diff --git a/apps/dm_new/public/images/landing/drop_arrow.svg b/apps/dm_new/public/images/landing/drop_arrow.svg new file mode 100644 index 000000000..faebd4d9e --- /dev/null +++ b/apps/dm_new/public/images/landing/drop_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/dm_new/public/images/landing/eu_01.png b/apps/dm_new/public/images/landing/eu_01.png new file mode 100644 index 000000000..05de059c8 Binary files /dev/null and b/apps/dm_new/public/images/landing/eu_01.png differ diff --git a/apps/dm_new/public/images/landing/hero_bg.jpg b/apps/dm_new/public/images/landing/hero_bg.jpg new file mode 100644 index 000000000..6363e360b Binary files /dev/null and b/apps/dm_new/public/images/landing/hero_bg.jpg differ diff --git a/apps/dm_new/public/images/landing/icon_left_arrow.png b/apps/dm_new/public/images/landing/icon_left_arrow.png new file mode 100644 index 000000000..374f668f5 Binary files /dev/null and b/apps/dm_new/public/images/landing/icon_left_arrow.png differ diff --git a/apps/dm_new/public/images/landing/icon_login.png b/apps/dm_new/public/images/landing/icon_login.png new file mode 100644 index 000000000..392c94f7d Binary files /dev/null and b/apps/dm_new/public/images/landing/icon_login.png differ diff --git a/apps/dm_new/public/images/landing/icon_logout.png b/apps/dm_new/public/images/landing/icon_logout.png new file mode 100644 index 000000000..f1d1093dd Binary files /dev/null and b/apps/dm_new/public/images/landing/icon_logout.png differ diff --git a/apps/dm_new/public/images/landing/icon_right_arrow.png b/apps/dm_new/public/images/landing/icon_right_arrow.png new file mode 100644 index 000000000..78f8949c8 Binary files /dev/null and b/apps/dm_new/public/images/landing/icon_right_arrow.png differ diff --git a/apps/dm_new/public/images/landing/intro/domestic_textbox.png b/apps/dm_new/public/images/landing/intro/domestic_textbox.png new file mode 100644 index 000000000..2fa8c0344 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/domestic_textbox.png differ diff --git a/apps/dm_new/public/images/landing/intro/greywater_intro.png b/apps/dm_new/public/images/landing/intro/greywater_intro.png new file mode 100644 index 000000000..be08b8df3 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/greywater_intro.png differ diff --git a/apps/dm_new/public/images/landing/intro/greywater_mandatory.png b/apps/dm_new/public/images/landing/intro/greywater_mandatory.png new file mode 100644 index 000000000..2e26c928e Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/greywater_mandatory.png differ diff --git a/apps/dm_new/public/images/landing/intro/greywater_standard.png b/apps/dm_new/public/images/landing/intro/greywater_standard.png new file mode 100644 index 000000000..8d613467c Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/greywater_standard.png differ diff --git a/apps/dm_new/public/images/landing/intro/law_timeline.png b/apps/dm_new/public/images/landing/intro/law_timeline.png new file mode 100644 index 000000000..a232aa2da Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/law_timeline.png differ diff --git a/apps/dm_new/public/images/landing/intro/linkage.png b/apps/dm_new/public/images/landing/intro/linkage.png new file mode 100644 index 000000000..cca2007ee Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/linkage.png differ diff --git a/apps/dm_new/public/images/landing/intro/performance_left.png b/apps/dm_new/public/images/landing/intro/performance_left.png new file mode 100644 index 000000000..6efe9651a Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/performance_left.png differ diff --git a/apps/dm_new/public/images/landing/intro/policy_timeline.png b/apps/dm_new/public/images/landing/intro/policy_timeline.png new file mode 100644 index 000000000..e21c2a2e9 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/policy_timeline.png differ diff --git a/apps/dm_new/public/images/landing/intro/rainreuse_intro.png b/apps/dm_new/public/images/landing/intro/rainreuse_intro.png new file mode 100644 index 000000000..c07be6584 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/rainreuse_intro.png differ diff --git a/apps/dm_new/public/images/landing/intro/rainreuse_mandatory.png b/apps/dm_new/public/images/landing/intro/rainreuse_mandatory.png new file mode 100644 index 000000000..4cc6c00de Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/rainreuse_mandatory.png differ diff --git a/apps/dm_new/public/images/landing/intro/rainwater_design.png b/apps/dm_new/public/images/landing/intro/rainwater_design.png new file mode 100644 index 000000000..b0f4fb02b Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/rainwater_design.png differ diff --git a/apps/dm_new/public/images/landing/intro/rainwater_standards.png b/apps/dm_new/public/images/landing/intro/rainwater_standards.png new file mode 100644 index 000000000..9b27f5382 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/rainwater_standards.png differ diff --git a/apps/dm_new/public/images/landing/intro/rel_note.png b/apps/dm_new/public/images/landing/intro/rel_note.png new file mode 100644 index 000000000..ea9f634c6 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/rel_note.png differ diff --git a/apps/dm_new/public/images/landing/intro/treatedwater_intro.png b/apps/dm_new/public/images/landing/intro/treatedwater_intro.png new file mode 100644 index 000000000..f069100aa Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/treatedwater_intro.png differ diff --git a/apps/dm_new/public/images/landing/intro/treatedwater_mandatory.png b/apps/dm_new/public/images/landing/intro/treatedwater_mandatory.png new file mode 100644 index 000000000..dbae8be70 Binary files /dev/null and b/apps/dm_new/public/images/landing/intro/treatedwater_mandatory.png differ diff --git a/apps/dm_new/public/images/landing/logo.png b/apps/dm_new/public/images/landing/logo.png new file mode 100644 index 000000000..5903da2da Binary files /dev/null and b/apps/dm_new/public/images/landing/logo.png differ diff --git a/apps/dm_new/public/images/landing/overview_01.png b/apps/dm_new/public/images/landing/overview_01.png new file mode 100644 index 000000000..14ae574ba Binary files /dev/null and b/apps/dm_new/public/images/landing/overview_01.png differ diff --git a/apps/dm_new/public/images/landing/rain_01.png b/apps/dm_new/public/images/landing/rain_01.png new file mode 100644 index 000000000..cd6ac34ff Binary files /dev/null and b/apps/dm_new/public/images/landing/rain_01.png differ diff --git a/apps/dm_new/public/images/landing/scroll_down.svg b/apps/dm_new/public/images/landing/scroll_down.svg new file mode 100644 index 000000000..28640dd4e --- /dev/null +++ b/apps/dm_new/public/images/landing/scroll_down.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/dm_new/public/images/landing/section_03.png b/apps/dm_new/public/images/landing/section_03.png new file mode 100644 index 000000000..8dbe15b2c Binary files /dev/null and b/apps/dm_new/public/images/landing/section_03.png differ diff --git a/apps/dm_new/public/images/landing/sewage_01.png b/apps/dm_new/public/images/landing/sewage_01.png new file mode 100644 index 000000000..26768caf2 Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_01.png differ diff --git a/apps/dm_new/public/images/landing/sewage_02.png b/apps/dm_new/public/images/landing/sewage_02.png new file mode 100644 index 000000000..608f88960 Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_02.png differ diff --git a/apps/dm_new/public/images/landing/sewage_03.png b/apps/dm_new/public/images/landing/sewage_03.png new file mode 100644 index 000000000..f22dcf3d0 Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_03.png differ diff --git a/apps/dm_new/public/images/landing/sewage_04.png b/apps/dm_new/public/images/landing/sewage_04.png new file mode 100644 index 000000000..40d89013b Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_04.png differ diff --git a/apps/dm_new/public/images/landing/sewage_05.png b/apps/dm_new/public/images/landing/sewage_05.png new file mode 100644 index 000000000..06f3da8b7 Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_05.png differ diff --git a/apps/dm_new/public/images/landing/sewage_06.png b/apps/dm_new/public/images/landing/sewage_06.png new file mode 100644 index 000000000..b299163a6 Binary files /dev/null and b/apps/dm_new/public/images/landing/sewage_06.png differ diff --git a/apps/dm_new/public/images/landing/singapore_01.png b/apps/dm_new/public/images/landing/singapore_01.png new file mode 100644 index 000000000..78cea99fb Binary files /dev/null and b/apps/dm_new/public/images/landing/singapore_01.png differ diff --git a/apps/dm_new/public/images/landing/taiwan_01.png b/apps/dm_new/public/images/landing/taiwan_01.png new file mode 100644 index 000000000..a972930ab Binary files /dev/null and b/apps/dm_new/public/images/landing/taiwan_01.png differ diff --git a/apps/dm_new/public/images/landing/water_quality_ref_1.png b/apps/dm_new/public/images/landing/water_quality_ref_1.png new file mode 100644 index 000000000..bf10b5fb3 Binary files /dev/null and b/apps/dm_new/public/images/landing/water_quality_ref_1.png differ diff --git a/apps/dm_new/src/entities/auth/api/login-service.ts b/apps/dm_new/src/entities/auth/api/login-service.ts new file mode 100644 index 000000000..43ea1c33d --- /dev/null +++ b/apps/dm_new/src/entities/auth/api/login-service.ts @@ -0,0 +1,78 @@ +import type { LoginService, ILogin } from "../model/login-interface"; +import { tokenManager } from "../lib/token-manager"; +import {PROXY_HOST, ACCESS_TOKEN, AUTH_USER_ID,SITE_ID, CLAIM_NAME, ENDPOINT, JWT_EXPIRATION_TIME } from "../../../shared/config"; + +export class LoginApiService implements LoginService { + private static instance: LoginApiService; + + private constructor() {} + + public static getInstance(): LoginApiService { + if (!LoginApiService.instance) { + LoginApiService.instance = new LoginApiService(); + } + return LoginApiService.instance; + } + + public async login(payload: ILogin): Promise { + try { + const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, { + method: "POST", + body: JSON.stringify({ + userId: payload.userId, + password: payload.password, + }), + }); + // const data = response.headers.get(ACCESS_TOKEN); + const accessToken = response.headers.get(ACCESS_TOKEN); + const tokenId = response.headers.get(AUTH_USER_ID); + + console.log('accessToken ', accessToken) + console.log('tokenId ', tokenId) + + if (!accessToken || !tokenId) { + throw new Error("Missing required authentication tokens"); + } + + tokenManager.setToken(CLAIM_NAME, accessToken); + tokenManager.setToken(AUTH_USER_ID, tokenId); + tokenManager.setToken("site-id-key", SITE_ID); + tokenManager.setupRefreshTimer( + () => this.silentRefresh(), + JWT_EXPIRATION_TIME, + ); + } catch (error) { + + console.log(' login err ' , error) + throw new Error(`Login failed: ${error}`); + } + } + + + public async silentRefresh(): Promise { + console.log(' silentRefresh ') + + try { + const response = await fetch(PROXY_HOST+ + `${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, + { method: "PUT" }, + ); + + const data = await response.json(); + const accessToken = data[ACCESS_TOKEN]; + const tokenId = data[AUTH_USER_ID]; + + if (!accessToken || !tokenId) { + throw new Error("Missing required authentication tokens"); + } + + tokenManager.setToken(CLAIM_NAME, accessToken); + tokenManager.setToken(AUTH_USER_ID, tokenId); + } catch (error) { + tokenManager.clearAuthToken(); + throw new Error(`Silent refresh failed: ${error}`); + } + } +} + +export const LoginApi = LoginApiService.getInstance(); diff --git a/apps/dm_new/src/entities/auth/index.ts b/apps/dm_new/src/entities/auth/index.ts new file mode 100644 index 000000000..9dc9dd368 --- /dev/null +++ b/apps/dm_new/src/entities/auth/index.ts @@ -0,0 +1,2 @@ +export { LoginApi } from "./api/login-service"; +//export { AuthWrapper } from "./lib/auth-wrapper"; diff --git a/apps/dm_new/src/entities/auth/lib/auth-wrapper.tsx b/apps/dm_new/src/entities/auth/lib/auth-wrapper.tsx new file mode 100644 index 000000000..678ff50d5 --- /dev/null +++ b/apps/dm_new/src/entities/auth/lib/auth-wrapper.tsx @@ -0,0 +1,45 @@ +// "use client"; + +// import { useEffect } from "react"; +// import { useRouter } from "next/navigation"; +// import { LoginApi } from "../api/login-service"; + +// const AuthWrapper = ({ children }: { children: React.ReactNode }) => { +// const router = useRouter(); +// const { userInfo, setUserInfo } = useUserStore((state) => state); + +// async function authCheck() { +// const userId = getCookie(AUTH_USER_ID); +// if (!userId) { +// throw new Error("Missing required authentication tokens"); +// } + +// const response = await client.get( +// `${ENDPOINT.USER_SERVICE.USERS}/info/me`, +// ); +// if (!userInfo) setUserInfo(response.data); +// } + +// useEffect(() => { +// (async () => { +// try { +// await authCheck(); +// } catch (error) { +// console.error(error); + +// try { +// await LoginApi.silentRefresh(); +// await authCheck(); +// } catch (refreshError) { +// console.error("Token refresh failed:", refreshError); +// setUserInfo(null); +// router.push("/auth/login"); +// } +// } +// })(); +// }, []); + +// return <>{children}; +// }; + +// export { AuthWrapper }; diff --git a/apps/dm_new/src/entities/auth/lib/token-manager.ts b/apps/dm_new/src/entities/auth/lib/token-manager.ts new file mode 100644 index 000000000..40d32f33e --- /dev/null +++ b/apps/dm_new/src/entities/auth/lib/token-manager.ts @@ -0,0 +1,59 @@ +import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; +import { getCookie, removeCookie, setCookie } from "@/src/shared/lib/cookie"; + +export class TokenManager { + private static instance: TokenManager; + private refreshTimer: NodeJS.Timeout | null = null; + + private constructor() {} + + public static getInstance(): TokenManager { + if (!TokenManager.instance) { + TokenManager.instance = new TokenManager(); + } + return TokenManager.instance; + } + + public getToken(key: string): string | undefined { + return getCookie(key); + } + + public setToken(key: string, value: string): void { + setCookie(key, value, { + secure: window.location.protocol === "https:", + sameSite: "lax", + }); + } + + public removeToken(key: string): void { + removeCookie(key); + } + + public setupRefreshTimer( + callback: () => Promise, + expirationTime: number, + ): void { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + } + + this.refreshTimer = setTimeout(() => { + callback().catch((error) => { + console.error("Silent refresh failed:", error); + this.clearAuthToken(); + }); + }, expirationTime - 60000); + } + + public clearAuthToken(): void { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = null; + } + + this.removeToken(CLAIM_NAME); + this.removeToken(AUTH_USER_ID); + } +} + +export const tokenManager = TokenManager.getInstance(); diff --git a/apps/dm_new/src/entities/auth/model/login-interface.ts b/apps/dm_new/src/entities/auth/model/login-interface.ts new file mode 100644 index 000000000..5aaf53044 --- /dev/null +++ b/apps/dm_new/src/entities/auth/model/login-interface.ts @@ -0,0 +1,9 @@ +export interface ILogin { + userId: string; + password: string; +} + +export interface LoginService { + login(payload: ILogin): Promise; + silentRefresh(): Promise; +} diff --git a/apps/dm_new/src/shared/api/client.ts b/apps/dm_new/src/shared/api/client.ts new file mode 100644 index 000000000..cf37e6671 --- /dev/null +++ b/apps/dm_new/src/shared/api/client.ts @@ -0,0 +1,11 @@ +import { Client } from "@common/network"; +import { BASE_URL } from "../config/api"; +import { SITE_ID } from "../config/env"; +import { setCookie } from "../lib"; + +setCookie("site-id-key", SITE_ID); + +export const client = Client.getInstance({ + baseURL: BASE_URL, + siteId: SITE_ID, +}); diff --git a/apps/dm_new/src/shared/api/index.ts b/apps/dm_new/src/shared/api/index.ts new file mode 100644 index 000000000..b7b9d6121 --- /dev/null +++ b/apps/dm_new/src/shared/api/index.ts @@ -0,0 +1 @@ +export { client } from "./client"; diff --git a/apps/dm_new/src/shared/config/api.ts b/apps/dm_new/src/shared/config/api.ts new file mode 100644 index 000000000..a531bed85 --- /dev/null +++ b/apps/dm_new/src/shared/config/api.ts @@ -0,0 +1,58 @@ +import { PROXY_HOST } from "./env"; + +export const BASE_URL = `${PROXY_HOST}/server`; + +export const ENDPOINT = { + ATTACH_SERVICE: { + TUS_UPLOAD: "/attach-service/api/v1/tus/upload", + EDITOR_UPLOAD: "/attach-service/api/v1/upload/editor", + IMAGES: "/attach-service/api/v1/images", + MOVIE_THUMBNAIL: "/attach-service/api/v1/movie/thumbnail", + ATTACHMENTS: "/attach-service/api/v1/atfl/attachments", + DOWNLOAD: "/attach-service/api/v1/atflcd/download", + STREAM_VIDEO: "/attach-service/api/v1/stream/video", + }, + USER_SERVICE: { + LOGIN: "/user-service/login", + USERS: "/user-service/api/v1/users", + SILENT_REFRESH: "/user-service/api/v1/users/token/refresh", + AUTHORIZATIONS: "/user-service/api/v1/authorizations", + ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations", + ROLES: "/user-service/api/v1/roles", + + }, + CMS_SERVICE: { + DMSITES:'cms-service/api/v1/menu/anonymous/user/DM', + SITES: "/cms-service/api/v1/sites", + IPS: "/cms-service/api/v1/site-ips", + DOMAINS: "/cms-service/api/v1/site-dmns", + POSTINGS: "/cms-service/api/v1/site-pstgs", + CODE: "/cms-service/api/v1/code/cache", + MENUS: "/cms-service/api/v1/menus", + MENU_ROLES: "/cms-service/api/v1/menu-roles", + MENU_AUTHRTS: "/cms-service/api/v1/menu-authrts", + CONTENTS: "/cms-service/api/v1/contents", + CONTENTS_DETAILS: "/cms-service/api/v1/contents-details", + ORGANIZATIONS: "/cms-service/api/v1/ognzs", + USER_ORGANIZATION: "/cms-service/api/v1/user-ognz", + IMAGES: "/cms-service/api/v1/images", + BANNERS: "/cms-service/api/v1/bnrs", + TEMPLATE: "/cms-service/api/v1/tmplt", + TERMS: "/cms-service/api/v1/trms", + LWPO_CODE: "/cms-service/api/v1/lwpo-cds", + UP_CODE: "/cms-service/api/v1/up-codes", + POPUPS: "/cms-service/api/v1/popups", + SCHEDULES: "/cms-service/api/v1/schdls", + SURVEY: "/cms-service/api/v1/srvy", + SURVEY_QUESTION: "/cms-service/api/v1/srvy-qitem", + SURVEY_ARTICLE: "/cms-service/api/v1/srvy-artcl", + }, + BOARD_SERVICE: { + BBS: "/board-service/api/v1/bbs", + BBS_TEMPLATES: "/board-service/api/v1/bbsTmplt", + POSTS: "/board-service/api/v1/posts", + POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts", + }, +}; + +export const JWT_EXPIRATION_TIME = 1800000; diff --git a/apps/dm_new/src/shared/config/env.ts b/apps/dm_new/src/shared/config/env.ts new file mode 100644 index 000000000..a9011b90e --- /dev/null +++ b/apps/dm_new/src/shared/config/env.ts @@ -0,0 +1,13 @@ +export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "DM"; +export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; +export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; +export const SERVER_API_DM_URL = process.env.NEXT_PUBLIC_DM_SERVER_API_URL ||'http://con.mind-one.co.kr:11035/api/v1'; +export const SERVER_API_FO_URL = process.env.NEXT_PUBLIC_FO_SERVER_API_URL||' http://con.mind-one.co.kr:11082/api/v1'; +export const SERVER_API_WR_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL||' http://con.mind-one.co.kr:11080/api/v1'; +export const SERVER_API_CS_URL = process.env.NEXT_PUBLIC_CS_SERVER_API_URL||'http://dev.onasys.co.kr:8000'; +export const SERVER_API_CS_TUS_URL = process.env.NEXT_PUBLIC_CS_TUS_UPLOAD ||'/attach-service/api/v1/tus/upload' +export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; +export const REFRESH_TOKEN =process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token"; +export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id"; +export const SERVER_SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL; \ No newline at end of file diff --git a/apps/dm_new/src/shared/config/index.ts b/apps/dm_new/src/shared/config/index.ts new file mode 100644 index 000000000..3ac40ddb4 --- /dev/null +++ b/apps/dm_new/src/shared/config/index.ts @@ -0,0 +1,2 @@ +export * from "./api"; +export * from "./env"; \ No newline at end of file diff --git a/apps/dm_new/src/shared/lib/cookie.ts b/apps/dm_new/src/shared/lib/cookie.ts new file mode 100644 index 000000000..48254bb18 --- /dev/null +++ b/apps/dm_new/src/shared/lib/cookie.ts @@ -0,0 +1,30 @@ +import Cookies from "js-cookie"; + + +export function getCookie(name: string): string | undefined { + if (typeof window === "undefined") { + return undefined; + } + return Cookies.get(name); +} + +export function setCookie( + name: string, + value: string, + options?: Cookies.CookieAttributes, +): void { + if (typeof window === "undefined") { + return; + } + Cookies.set(name, value, options); +} + +export function removeCookie( + name: string, + options?: Cookies.CookieAttributes, +): void { + if (typeof window === "undefined") { + return; + } + Cookies.remove(name, options); +} diff --git a/apps/dm_new/src/shared/lib/index.ts b/apps/dm_new/src/shared/lib/index.ts new file mode 100644 index 000000000..c9cff1a80 --- /dev/null +++ b/apps/dm_new/src/shared/lib/index.ts @@ -0,0 +1,8 @@ +export { getCookie, setCookie, removeCookie } from "./cookie"; +// export { useMutationWrapper } from "./use-mutation-wrapper"; +// export { useAlertStore } from "./use-alert-store"; +// export { useDataTable } from "./use-data-table"; +export { useMenuStore } from "./use-menu-store"; +// export { useUserStore } from "./use-user-store"; +// export { usePrompt } from "./use-prompt"; +// export { useSelectPrompt } from "./use-select-prompt"; diff --git a/apps/dm_new/src/shared/lib/use-menu-store.ts b/apps/dm_new/src/shared/lib/use-menu-store.ts new file mode 100644 index 000000000..66e41c76c --- /dev/null +++ b/apps/dm_new/src/shared/lib/use-menu-store.ts @@ -0,0 +1,14 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import type { MenuStore } from "../store/menu-store"; +import { StoreContext } from "../providers/store-provider"; + +export const useMenuStore = (selector: (store: MenuStore) => T): T => { + const storeContext = useContext(StoreContext); + + if (!storeContext) { + throw new Error("useMenuStore must be used within StoreProvider"); + } + + return useStore(storeContext.menuStore, selector); +}; diff --git a/apps/dm_new/src/shared/providers/index.ts b/apps/dm_new/src/shared/providers/index.ts new file mode 100644 index 000000000..fa8320fe7 --- /dev/null +++ b/apps/dm_new/src/shared/providers/index.ts @@ -0,0 +1,2 @@ +export { StoreProvider } from "./store-provider"; +export { PromptProvider } from "./prompt-provider"; diff --git a/apps/dm_new/src/shared/providers/prompt-provider.tsx b/apps/dm_new/src/shared/providers/prompt-provider.tsx new file mode 100644 index 000000000..71b86388c --- /dev/null +++ b/apps/dm_new/src/shared/providers/prompt-provider.tsx @@ -0,0 +1,61 @@ +import { createContext, useState } from "react"; + +type SelectData = { + label: string; + value: string; +}; + +interface PromptContext { + message?: string; + selectData?: SelectData[]; + selectionGuide?: string; + resolve?: ({ + resolved, + value, + }: { + resolved: boolean; + value?: string; + }) => void; + setMessage: (message: string) => void; + setSelectData: (selectData: SelectData[]) => void; + setSelectionGuide: (message: string) => void; + setResolve: (resolve: () => void) => void; +} + +export const PromptContext = createContext({ + message: "", + selectData: [], + selectionGuide: "", + resolve() {}, + setMessage() {}, + setSelectData() {}, + setSelectionGuide() {}, + setResolve() {}, +}); + +export const PromptProvider = ({ children }: { children: React.ReactNode }) => { + const [message, setMessage] = useState(); + const [selectData, setSelectData] = useState([]); + const [selectionGuide, setSelectionGuide] = useState(); + const [resolve, setResolve] = + useState< + ({ resolved, value }: { resolved: boolean; value?: string }) => void + >(); + + return ( + + {children} + + ); +}; diff --git a/apps/dm_new/src/shared/providers/store-provider.tsx b/apps/dm_new/src/shared/providers/store-provider.tsx new file mode 100644 index 000000000..a0ec17b61 --- /dev/null +++ b/apps/dm_new/src/shared/providers/store-provider.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { createContext, useRef } from "react"; +import { + createAlertStore, + createAttachmentStore, + createMenuStore, + createUserStore, +} from "../store"; + +type StoreContextType = { + alertStore: ReturnType; + attachmentStore: ReturnType; + menuStore: ReturnType; + userStore: ReturnType; +}; + +export const StoreContext = createContext( + undefined, +); + +export const StoreProvider = ({ children }: { children: React.ReactNode }) => { + const storeRef = useRef(null); + if (storeRef.current === null) { + storeRef.current = { + alertStore: createAlertStore(), + attachmentStore: createAttachmentStore(), + menuStore: createMenuStore(), + userStore: createUserStore(), + }; + } + + return ( + + {children} + + ); +}; diff --git a/apps/dm_new/src/shared/store/alert-store.ts b/apps/dm_new/src/shared/store/alert-store.ts new file mode 100644 index 000000000..3bba65848 --- /dev/null +++ b/apps/dm_new/src/shared/store/alert-store.ts @@ -0,0 +1,25 @@ +import { createStore } from "zustand/vanilla"; + +export type AlertState = { + message: string; + callback?: () => void; +}; + +export type AlertActions = { + setMessage: (message: string, callback?: () => void) => void; +}; + +export type AlertStore = AlertState & AlertActions; + +export const defaultInitState: AlertState = { + message: "", + callback: undefined, +}; + +export const createAlertStore = (initState: AlertState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + setMessage: (message: string, callback?: () => void) => + set(() => ({ message, callback })), + })); +}; diff --git a/apps/dm_new/src/shared/store/attachment-store.ts b/apps/dm_new/src/shared/store/attachment-store.ts new file mode 100644 index 000000000..faa700949 --- /dev/null +++ b/apps/dm_new/src/shared/store/attachment-store.ts @@ -0,0 +1,25 @@ +import { createStore } from "zustand/vanilla"; + +export type AttachmentState = { + attachmentFileCode: string; +}; + +export type AttachmentActions = { + setAttachmentFileCode: (attachmentFileCode: string) => void; +}; + +export type AttachmentStore = AttachmentState & AttachmentActions; + +export const defaultInitState: AttachmentState = { + attachmentFileCode: "", +}; + +export const createAttachmentStore = ( + initState: AttachmentState = defaultInitState, +) => { + return createStore()((set) => ({ + ...initState, + setAttachmentFileCode: (attachmentFileCode: string) => + set({ attachmentFileCode }), + })); +}; diff --git a/apps/dm_new/src/shared/store/index.ts b/apps/dm_new/src/shared/store/index.ts new file mode 100644 index 000000000..a41d6f928 --- /dev/null +++ b/apps/dm_new/src/shared/store/index.ts @@ -0,0 +1,4 @@ +export { createAlertStore } from "./alert-store"; +export { createAttachmentStore } from "./attachment-store"; +export { createMenuStore, type BbsType } from "./menu-store"; +export { createUserStore, type IUserInfo } from "./user-store"; diff --git a/apps/dm_new/src/shared/store/menu-store.ts b/apps/dm_new/src/shared/store/menu-store.ts new file mode 100644 index 000000000..8536011ee --- /dev/null +++ b/apps/dm_new/src/shared/store/menu-store.ts @@ -0,0 +1,93 @@ +import { createStore } from "zustand/vanilla"; + +export const BBS_TYPE = { + BBSNOTICE: "BBSNOTICE", + BBSNORMAL: "BBSNORMAL", + BBSFAQ: "BBSFAQ", + BBSIMAGE: "BBSIMAGE", + BBSMOVIE: "BBSMOVIE", +} as const; + +export type BbsType = (typeof BBS_TYPE)[keyof typeof BBS_TYPE]; + +export interface IBbs { + bbsId: number; + bbsTypeCd: BbsType; + bbsTypeNm: string; + bbsNm: string; + bbsExpln: string; + rfrncUseYn: string; + atflUseYn: string; + atflMaxCnt: number; + atflMaxFileSz: number; + ansUseYn: string; + ansUseAuthrt: string; + cmntUseYn: string; + dgstfnEvlUseYn: string; + smsUseYn: string; + upndFixUseYn: string; + pstgPrdUseYn: string; + popupUseYn: string; + delYn: string; + useYn: string; + mdfcnDt: string; + mdfrNm: string; +} + +interface IMenu { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: IMenu[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; +} + +interface IAuthority { + mngAuthrtYn: string; + inqAuthrtYn: string; + regAuthrtYn: string; + mdfcnAuthrtYn: string; + delAuthrtYn: string; +} + +export type MenuState = { + bbsInfo: IBbs | null; + currentMenuInfo: IMenu | null; + pageMenuAuthority: IAuthority | null; +}; + +export type MenuActions = { + setBbsInfo: (bbs: IBbs) => void; + setCurrentMenuInfo: (menu: IMenu) => void; + setPageMenuAuthority: (authority: IAuthority) => void; +}; + +export type MenuStore = MenuState & MenuActions; + +export const defaultInitState: MenuState = { + currentMenuInfo: null, + bbsInfo: null, + pageMenuAuthority: null, +}; + +export const createMenuStore = (initState: MenuState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + setBbsInfo: (bbs: IBbs) => set({ bbsInfo: bbs }), + setCurrentMenuInfo: (menu: IMenu) => set({ currentMenuInfo: menu }), + setPageMenuAuthority: (authority: IAuthority) => + set({ pageMenuAuthority: authority }), + })); +}; diff --git a/apps/dm_new/src/shared/store/user-store.ts b/apps/dm_new/src/shared/store/user-store.ts new file mode 100644 index 000000000..65d897cf1 --- /dev/null +++ b/apps/dm_new/src/shared/store/user-store.ts @@ -0,0 +1,57 @@ +import { createStore } from "zustand/vanilla"; + +export interface IUserInfo { + regDt: string; + mdfcnDt: string; + rgtr: string; + mdfr: string; + adminRoleYn: string; + userUnqId: string; + userId: string; + userName: string; + email: string; + encryptedPassword: string; + refreshToken: string; + userStateCode: string; + lastLoginDate: string; + loginFailCount: number; + pswdHnt: string; + pswdAns: string; + mphn: string; + telno: string; + zip: string; + addr: string; + daddr: string; + laeSeCd: string; + ogdpSeCd: string; + aprvYn: string; + autzrId: string; + expryYmd: string; + lastPswdChgDt: string; + googleId: string; + kakaoId: string; + naverId: string; + isSocialUser: boolean; + hasPassword: boolean; +} + +export type UserState = { + userInfo: IUserInfo | null; +}; + +export type UserActions = { + setUserInfo: (userInfo: IUserInfo | null) => void; +}; + +export type UserStore = UserState & UserActions; + +export const defaultInitState: UserState = { + userInfo: null, +}; + +export const createUserStore = (initState: UserState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + setUserInfo: (userInfo: IUserInfo | null) => set(() => ({ userInfo })), + })); +}; diff --git a/apps/dm_new/tailwind.config.ts b/apps/dm_new/tailwind.config.ts new file mode 100644 index 000000000..42cfc69f5 --- /dev/null +++ b/apps/dm_new/tailwind.config.ts @@ -0,0 +1,69 @@ +import baseConfig from "../../packages/common/components/tailwind.config"; + +import type { Config } from "tailwindcss"; + +export default { + darkMode: ["class"], + presets: [baseConfig], + content: [ + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "../../packages/common/components/src/ui/**/*.{js,ts,jsx,tsx,mdx}", + "../../packages/common/business_components/src/ui/**/*.{js,ts,jsx,tsx,mdx}", + ], + plugins: [require("tailwindcss-animate")], + theme: { + extend: { + fontFamily: { + NotoSansKR: ['NotoSansKR'], + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } + } +} satisfies Config; diff --git a/apps/dm_new/tsconfig.json b/apps/dm_new/tsconfig.json new file mode 100644 index 000000000..ba8cd2c2f --- /dev/null +++ b/apps/dm_new/tsconfig.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noImplicitAny": false, + "baseUrl": ".", + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "strictNullChecks": true, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"], + "@common/components/*": ["../../packages/common/components/src/*"], + "@common/components": ["../../packages/common/components/src/index"], + "@common/business_components/*": [ + "../../packages/common/business_components/src/*" + ], + "@common/business_components": [ + "../../packages/common/business_components/src/index" + ], + "@common/network": ["../../packages/common/network"] + } + }, + "include": [ + ".eslintrc.*", + "next-env.d.ts", + "**/*.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.mts", + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "**/*.jsx", + "**/*.json", + ".next/types/**/*.ts" + ], + "exclude": ["**/node_modules", "**/.*/*"] +} diff --git a/apps/dm_new/types/style.d.ts b/apps/dm_new/types/style.d.ts new file mode 100644 index 000000000..cbe652dbe --- /dev/null +++ b/apps/dm_new/types/style.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/apps/fo/.env.local b/apps/fo/.env.local index bf6d56f58..646d80c2c 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -5,9 +5,10 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 +NEXT_PUBLIC_SUB_DOMAIN = http://localhost:13000 #NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://localhost:3001/auth/login NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/ +NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver @@ -15,7 +16,8 @@ NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com/dc-service NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +# NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = / #NEXT_PUBLIC_SERVER_API_URL = http://localhost:8883/operation-service diff --git a/apps/fo/.env.mindone b/apps/fo/.env.mindone index d2258aaf3..f014168c2 100644 --- a/apps/fo/.env.mindone +++ b/apps/fo/.env.mindone @@ -5,6 +5,7 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 +NEXT_PUBLIC_SUB_DOMAIN = http://con.mind-one.co.kr:13000 NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login #NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service diff --git a/apps/fo/.env.production b/apps/fo/.env.production index 45e179066..bd0d0f04b 100644 --- a/apps/fo/.env.production +++ b/apps/fo/.env.production @@ -8,13 +8,13 @@ NEXT_PUBLIC_REFRESH_TOKEN = refresh-token #NEXT_PUBLIC_PROXY_HOST = https://devlarva.hasuon.or.kr #NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service -NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 -NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service -NEXT_PUBLIC_NGII_URL = http://172.29.143.87 +NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service +NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login \ No newline at end of file diff --git a/apps/fo/app/auth/login/route.ts b/apps/fo/app/auth/login/route.ts index d954cbaa1..65bbce15e 100644 --- a/apps/fo/app/auth/login/route.ts +++ b/apps/fo/app/auth/login/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const hostname = request.nextUrl.hostname; - const redirectUrl = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "https://queen.hasuon.or.kr"; + const redirectUrl = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "https://portal.hasuon.or.kr"; if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") { return NextResponse.redirect(new URL("/", request.url)); // 로컬 및 개발 환경 diff --git a/apps/fo/app/components/common/CalcRainEffect.tsx b/apps/fo/app/components/common/CalcRainEffect.tsx index beca33f78..898bea0ba 100644 --- a/apps/fo/app/components/common/CalcRainEffect.tsx +++ b/apps/fo/app/components/common/CalcRainEffect.tsx @@ -9,67 +9,111 @@ * @author jsyang */ -export default function CalcRainEffect(data : any, weather='ALL') { - /* console.log("%c Calculate RainEffect !" , "background:blue;color:white"); - console.log("%c Data Length :"+data.length+" Weather : "+weather , "background:yellow;color:blue");*/ - // 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일) - for(let idx = 0; idx < data.length; idx++) { +export default function CalcRainEffect(data: any, weather = 'ALL') { + const rtnDataAll: any = []; + const rtnDataSunny: any = []; + const rtnDataRain: any = []; + let rtnData: any = []; + let rainEffectStd = 0; + for (let idx = 0; idx < data.length; idx++) { const item = data[idx]; - if(item){ - if(item.infoRnDay && Number(item.infoRnDay) >= 3) { // 강우량이 3 이상인 경우 - item.rainEffectDay = 1; - } else { - item.rainEffectDay = 0; - } - - if(item.infoRnDay == 'x'){ - item.infoRnDay = '0.0'; - item.toBeInfoRnDay = '0.0'; - }else{ - item.infoRnDay = Number(item.infoRnDay).toFixed(1); - item.toBeInfoRnDay = Number(item.infoRnDay).toFixed(1); - } + if (item.infoRnDay == null || item.infoRnDay == 'x' || item.infoRnDay == '') item.infoRnDay = 0; + if (Number(item.infoRnDay) >= 3) { // 강우일 + rainEffectStd = 2; + item.toBeInfoRnDay = item.infoRnDay; + } else { + rainEffectStd--; } - } - // 이틀 전, 하루 전이 강우영향일(3 이상)인 경우 해당 날도 강우영향일로 설정 - for(let idx = 0; idx < data.length; idx++) { - if(data[idx]) { - // 하루 전이 강우영향일(3 이상)인 경우 - if(idx > 0 && data[idx-1]?.infoRnDay && Number(data[idx-1]?.infoRnDay) >= 3) { - data[idx]!.rainEffectDay = 1; - } - // 이틀 전이 강우영향일(3 이상)인 경우 - if(idx > 1 && data[idx-2]?.infoRnDay && Number(data[idx-2]?.infoRnDay) >= 3) { - data[idx]!.rainEffectDay = 1; - } - } - } - - for(let idx = 0; idx < data.length; idx++) { - if(data[idx] && data[idx]?.rainEffectDay === 1) { - if(idx > 1){ - // 전날 비가 왔고 오늘 강우량 3미만인 경우 전날 강우량이 3이상이면 오늘 toBeInfoRnDay을 전날 강우량으로 설정 - if(data[idx-1]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx-1]?.infoRnDay.toString().trim()) >= 3) { - data[idx]!.toBeInfoRnDay = data[idx-1]!.infoRnDay; - } - } - if(idx > 2){ + if (rainEffectStd < 0) { // 청천일 + item.rainEffectDay = 0; + rtnDataSunny.push(item); + } else if (rainEffectStd >= 0) { // 강우일 + 강우영향일 + item.rainEffectDay = 1; + if (idx > 2) { // 전전날 비가 왔고 오늘 강우량 3미만인 경우 전전날 강우량이 3이상이면 오늘 toBeInfoRnDay을 전전날 강우량으로 설정 - if(data[idx-2]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx-2]?.infoRnDay.toString().trim()) >= 3) { - data[idx]!.toBeInfoRnDay = data[idx-2]!.infoRnDay; + if (data[idx - 2]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx - 2]?.infoRnDay.toString().trim()) >= 3) { + data[idx]!.toBeInfoRnDay = data[idx - 2]!.infoRnDay; } } + if (idx > 1) { + // 전날 비가 왔고 오늘 강우량 3미만인 경우 전날 강우량이 3이상이면 오늘 toBeInfoRnDay을 전날 강우량으로 설정 + if (data[idx - 1]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx - 1]?.infoRnDay.toString().trim()) >= 3) { + data[idx]!.toBeInfoRnDay = data[idx - 1]!.infoRnDay; + } + } + rtnDataRain.push(item); } + + rtnDataAll.push(item); } - let rtnData : any = []; + + // // 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일) + // for(let idx = 0; idx < data.length; idx++) { + // const item = data[idx]; + // if(item){ + // if(item.infoRnDay && Number(item.infoRnDay) >= 3) { // 강우량이 3 이상인 경우 + // item.rainEffectDay = 1; + // } else { + // item.rainEffectDay = 0; + // } + + // if(item.infoRnDay == 'x'){ + // item.infoRnDay = '0.0'; + // item.toBeInfoRnDay = '0.0'; + // }else{ + // item.infoRnDay = Number(item.infoRnDay).toFixed(1); + // item.toBeInfoRnDay = Number(item.infoRnDay).toFixed(1); + // } + // } + // } + + // // 이틀 전, 하루 전이 강우영향일(3 이상)인 경우 해당 날도 강우영향일로 설정 + // for(let idx = 0; idx < data.length; idx++) { + // if(data[idx]) { + // // 하루 전이 강우영향일(3 이상)인 경우 + // if(idx > 0 && data[idx-1]?.infoRnDay && Number(data[idx-1]?.infoRnDay) >= 3) { + // data[idx]!.rainEffectDay = 1; + // } + // // 이틀 전이 강우영향일(3 이상)인 경우 + // if(idx > 1 && data[idx-2]?.infoRnDay && Number(data[idx-2]?.infoRnDay) >= 3) { + // data[idx]!.rainEffectDay = 1; + // } + // } + // } + + // for(let idx = 0; idx < data.length; idx++) { + // if(data[idx] && data[idx]?.rainEffectDay === 1) { + // if(idx > 1){ + // // 전날 비가 왔고 오늘 강우량 3미만인 경우 전날 강우량이 3이상이면 오늘 toBeInfoRnDay을 전날 강우량으로 설정 + // if(data[idx-1]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx-1]?.infoRnDay.toString().trim()) >= 3) { + // data[idx]!.toBeInfoRnDay = data[idx-1]!.infoRnDay; + // } + // } + // if(idx > 2){ + // // 전전날 비가 왔고 오늘 강우량 3미만인 경우 전전날 강우량이 3이상이면 오늘 toBeInfoRnDay을 전전날 강우량으로 설정 + // if(data[idx-2]?.infoRnDay && Number(data[idx]?.infoRnDay) < 3 && Number(data[idx-2]?.infoRnDay.toString().trim()) >= 3) { + // data[idx]!.toBeInfoRnDay = data[idx-2]!.infoRnDay; + // } + // } + // } + // } + + // if (weather === 'ALL') { + // rtnData = data; + // } else if (weather === 'SUNNY') { + // rtnData = data.filter((item: any) => item.rainEffectDay === 0); + // } else { + // rtnData = data.filter((item: any) => item.rainEffectDay === 1); + // } + if (weather === 'ALL') { - rtnData = data; + rtnData = rtnDataAll; } else if (weather === 'SUNNY') { - rtnData = data.filter((item: any) => item.rainEffectDay === 0); + rtnData = rtnDataSunny; } else { - rtnData = data.filter((item: any) => item.rainEffectDay === 1); + rtnData = rtnDataRain; } return rtnData; } \ No newline at end of file diff --git a/apps/fo/app/components/common/DataForm.tsx b/apps/fo/app/components/common/DataForm.tsx index 48b428e32..2c99129b2 100644 --- a/apps/fo/app/components/common/DataForm.tsx +++ b/apps/fo/app/components/common/DataForm.tsx @@ -4,12 +4,39 @@ item: Record; } -export const roundNumber = (value : any, decimals: number) => { +/** + * yyyymm 형태의 문자열을 받아 해당 월의 일수를 반환 + * @param {string} yyyymm - 예: "202502", "202412" + * @returns {number} 해당 월의 일수 (예: 28, 29, 30, 31) + */ +export const getDaysInMonth = (yyyymm: string) => { + if (typeof yyyymm !== 'string' || yyyymm.length !== 6) { + throw new Error("입력은 'yyyymm' 형식의 문자열이어야 합니다. 예: '202502'"); + } + + const year = parseInt(yyyymm.slice(0, 4), 10); // "2025" -> 2025 + const month = parseInt(yyyymm.slice(4, 6), 10); // "02" -> 2 + + if (isNaN(year) || isNaN(month) || month < 1 || month > 12) { + throw new Error("유효한 년/월이 아닙니다."); + } + + // Date(year, month, 0) -> 전달의 마지막 날 + // month에 '다음달'을 넣고, 일(day)에 0을 주면 해당 월의 마지막 날짜가 됨 + return new Date(year, month, 0).getDate(); +} + +export const roundNumber = (value: any, decimals: number) => { if (typeof value === 'number') { return addCommas((Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals)).toFixed(decimals)); - } else if (typeof value === 'string' && !isNaN(Number(value))) { - return addCommas(Number(value).toFixed(decimals)); + } else if (typeof value === 'string') { + // 콤마가 포함된 문자열 처리 ('1,083.0' 같은 경우) + const cleanedValue = value.replace(/,/g, ''); + if (!isNaN(Number(cleanedValue))) { + return addCommas(Number(cleanedValue).toFixed(decimals)); + } } + return value; // 변환 불가능한 경우 원래 값 반환 } export const addCommas = (value: string | number): any => { @@ -18,48 +45,48 @@ export const addCommas = (value: string | number): any => { index === 0 ? part : part.replace(/(\d{3})/g, '$1,') ).join('.'); } else if (typeof value === 'string') { - return value.includes('.')?value.split('.').map((part, index) => + return value.includes('.') ? value.split('.').map((part, index) => index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part - ).join('.'):String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ","); + ).join('.') : String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ","); } } export default function DataForm({ type, column, item }: DataFormProps) { - if(item) { + if (item) { const value = item[column]; - switch(type){ + switch (type) { case 'date': - if(value && typeof value === 'string' && value.length >= 8) + if (value && typeof value === 'string' && value.length >= 8 && !value.includes('-')) item[column] = value.substring(0, 4) + "-" + value.substring(4, 6) + "-" + value.substring(6, 8); - break; + break; case 'number': - if(value && value != '') { + if (value && value != '') { item[column] = value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } - break; + break; case 'float0': - if(value) { - item[column] = item[column] = String(value).includes('.')?String(Math.round(parseFloat(value.toString().replace(/,/g, '')) * 1) / 1).split('.').map((part, index) => - index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part - ).join('.') - : String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ","); + if (value) { + item[column] = item[column] = String(value).includes('.') ? String(Math.round(parseFloat(value.toString().replace(/,/g, '')) * 1) / 1).split('.').map((part, index) => + index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part + ).join('.') + : String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ","); } - break; + break; case 'float1': - if(value) { + if (value) { item[column] = roundNumber(value, 1); } - break; + break; case 'float2': - if(value) { + if (value) { item[column] = roundNumber(value, 2); } - break; + break; case 'float3': - if(value) { + if (value) { item[column] = roundNumber(value, 3); } - break; + break; } } } \ No newline at end of file diff --git a/apps/fo/app/components/common/FacilityTermModalHeader.tsx b/apps/fo/app/components/common/FacilityTermModalHeader.tsx index 52cee2ea3..a46b3652d 100644 --- a/apps/fo/app/components/common/FacilityTermModalHeader.tsx +++ b/apps/fo/app/components/common/FacilityTermModalHeader.tsx @@ -4,14 +4,14 @@ import ListDiv from '@common/business_components/ui/list_div'; import { Table, TableCell, TableHead, TableRow } from '@common/components/ui'; import { useCallback, useEffect, useRef, useState } from 'react'; -type FacilityTermModalHeaderProps = { - fcltyCd : string; +type FacilityTermModalHeaderProps = { // T는 반드시 msrDt: string 필드를 가져야 한다 + fcltyCd: string; fcltyName: string; tabName: string; searchYear: string; searchList: T[]; ref: React.RefObject>; - queryClick:(returnType:ReturnType) => void; + queryClick: (returnType: ReturnType) => void; } export type ReturnType = { @@ -21,27 +21,28 @@ export type ReturnType = { filteredList: T[]; } -export default function FacilityTermModalHeader({ +export default function FacilityTermModalHeader({ searchList, - fcltyName, - tabName, + fcltyName, + tabName, searchYear, queryClick, ref }: FacilityTermModalHeaderProps) { - const {Common} = useFoStore((state) => state); + const { Common } = useFoStore((state) => state); const { selectFlowPanel } = Common; const [startDate, setStartDate] = useState(new Date(Number(searchYear), 0, 1)); const [endDate, setEndDate] = useState(new Date(Number(searchYear), 11, 31)); const startPickerRef = useRef(null); const endPickerRef = useRef(null); - const [flowPanel, setFlowPanel] = useState(selectFlowPanel[0]?.val ?? '00'); + const [flowPanel, setFlowPanel] = useState(selectFlowPanel[0]?.val ?? '0'); // 강수구분 : 초기값 "전체" const [copySearchList, setCopySearchList] = useState(searchList); const [standSearchList, setStandSearchList] = useState(searchList); const isDateInRanges = (date: Date) => { const standYear = parseInt(searchYear); - const dateRanges:{start:Date, end:Date}[] = []; + const isLeapYear = (standYear % 4 === 0 && standYear % 100 !== 0) || (standYear % 400 === 0); + const dateRanges: { start: Date, end: Date }[] = []; if (tabName.includes("_summer")) { dateRanges.push({ @@ -51,7 +52,7 @@ export default function FacilityTermModalHeader({ } else if (tabName.includes("_winter")) { dateRanges.push({ start: new Date(standYear, 0, 1), - end: new Date(standYear, 1, 28) + end: new Date(standYear, 1, isLeapYear ? 29 : 28) }); } else { dateRanges.push({ @@ -70,67 +71,69 @@ export default function FacilityTermModalHeader({ useEffect(() => { let startDate = new Date(Number(searchYear), 0, 1); let endDate = new Date(Number(searchYear), 11, 31); + const standYear = Number(searchYear); + const isLeapYear = (standYear % 4 === 0 && standYear % 100 !== 0) || (standYear % 400 === 0); - if(tabName.includes("_summer")) { - startDate = new Date(Number(searchYear), 5, 1); - endDate = new Date(Number(searchYear), 7, 31); + if (tabName.includes("_summer")) { // 하절기 + startDate = new Date(Number(searchYear), 5, 1); // 6월 ~ + endDate = new Date(Number(searchYear), 7, 31); // 8월 setStartDate(startDate); setEndDate(endDate); - } else if (tabName.includes("_winter")) { - startDate = new Date(Number(searchYear), 0, 1); - endDate = new Date(Number(searchYear), 1, 28); + } else if (tabName.includes("_winter")) { // 동절기 + startDate = new Date(Number(searchYear), 0, 1); // 1월 ~ + endDate = new Date(Number(searchYear), 1, isLeapYear ? 29 : 28); // 2월 setStartDate(startDate); setEndDate(endDate); - } else { + } else { // 연간 startDate = new Date(Number(searchYear), 0, 1); endDate = new Date(Number(searchYear), 11, 31); setStartDate(startDate); setEndDate(endDate); } - + let filteredData: T[] = []; - if(tabName.includes("_rain")) { + if (tabName.includes("_rain")) { // 강우시 filteredData = searchList.filter((item) => { const itemDate = new Date((item as T & { msrDt: string }).msrDt); const itemDateOnly = new Date(itemDate.getFullYear(), itemDate.getMonth(), itemDate.getDate()); const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); const rainEffectDay = (item as T & { rainEffectDay: number }).rainEffectDay; - - if(flowPanel === '2'){ + + if (flowPanel === '2') { //3~10mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 3 && infoRnDay < 10; - } else if(flowPanel === '3'){ + } else if (flowPanel === '3') { //10~20mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 10 && infoRnDay < 20; - } else if(flowPanel === '4'){ + } else if (flowPanel === '4') { //20~30mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 20 && infoRnDay < 30; - } else if(flowPanel === '5'){ + } else if (flowPanel === '5') { //30~40mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 30 && infoRnDay < 40; - } else if(flowPanel === '6'){ + } else if (flowPanel === '6') { //40~50mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 40 && infoRnDay < 50; - } else if(flowPanel === '7'){ + } else if (flowPanel === '7') { //50~60mm미만 const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 50 && infoRnDay < 60; - } else if(flowPanel === '8'){ + } else if (flowPanel === '8') { const infoRnDay = (item as T & { toBeInfoRnDay: number }).toBeInfoRnDay; return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && infoRnDay >= 60; } else { - return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && (rainEffectDay ===1); + return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && (rainEffectDay === 1); } }); - } else if(tabName.includes("_chuncheon")) { + } else if (tabName.includes("_chuncheon")) { // 청천시 filteredData = searchList.filter((item) => { const itemDate = new Date((item as T & { msrDt: string }).msrDt); const itemDateOnly = new Date(itemDate.getFullYear(), itemDate.getMonth(), itemDate.getDate()); const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()); const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()); const rainEffectDay = (item as T & { rainEffectDay: number }).rainEffectDay; - return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && rainEffectDay==0; + return (itemDateOnly >= startDateOnly && itemDateOnly <= endDateOnly) && rainEffectDay == 0; }); } else { filteredData = searchList.filter((item) => { @@ -146,7 +149,7 @@ export default function FacilityTermModalHeader({ }, [searchList, searchYear, tabName, flowPanel]); const handleStartDateSelect = (day: Date | undefined) => { - if(day && day > endDate) { + if (day && day > endDate) { alert('종료일은 시작일 이전일 수 없습니다.'); return; } @@ -169,7 +172,7 @@ export default function FacilityTermModalHeader({ }; const handleEndDateSelect = (day: Date | undefined) => { - if(day && day < startDate) { + if (day && day < startDate) { alert('종료일은 시작일 이후일 수 없습니다.'); return; } @@ -190,7 +193,7 @@ export default function FacilityTermModalHeader({ }); setCopySearchList(filteredData); }; - + const fncSetFilteredList = useCallback(() => { queryClick({ startDate: startDate, @@ -203,9 +206,9 @@ export default function FacilityTermModalHeader({ useEffect(() => { fncSetFilteredList(); }, [fncSetFilteredList]); - + useEffect(() => { - if(ref && ref.current) { + if (ref && ref.current) { const customRef = ref.current; customRef.startDate = startDate; customRef.endDate = endDate; @@ -222,18 +225,18 @@ export default function FacilityTermModalHeader({ {'시설명'} {tabName === "tab_term_rain" ? {fcltyName} : - {fcltyName}} + {fcltyName}} {tabName === "tab_term_rain" && ( <> {'강수구분'}
- setFlowPanel(val.toString())} - className="mx-3 w-[150px]" - label="" + className="mx-3 w-[150px]" + label="" selectClass="w-full p-1 border rounded bg-white" />
@@ -244,7 +247,7 @@ export default function FacilityTermModalHeader({
- handleStartDateSelect(date as Date)} @@ -258,7 +261,7 @@ export default function FacilityTermModalHeader({ handleEndDateSelect(date as Date)} + onSelect={(date) => handleEndDateSelect(date as Date)} className="w-full p-1 border rounded bg-white" disabled={(date) => !isDateInRanges(date)} defaultMonth={endDate} diff --git a/apps/fo/app/page.tsx b/apps/fo/app/page.tsx index 26f528d0e..143dfdc90 100644 --- a/apps/fo/app/page.tsx +++ b/apps/fo/app/page.tsx @@ -11,25 +11,20 @@ export default function Home() { const checkToken = getCookie(AUTH_USER_ID); useEffect(() => { + const hostname = window.location.hostname; - if (!checkToken) { - // 외부 URL로 이동해야 할 경우 location.replace를 사용합니다. - if (window.location.hostname.includes("hasuon.or.kr")) { - location.replace(process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL||'/pages/dashboard'); - return; // 페이지 이동 후에는 더 이상 로직을 실행하지 않습니다. - } - - async function doLogin() { - try { - await LoginApi.login({ userId: "CLEANWATER", password: "gktneh12!@" }); - router.push("/pages/dashboard"); // location.href 대신 router.push 사용 - } catch (err) { - console.error("로그인 실패", err); + if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") { + if (!checkToken) { + async function doLogin() { + try { + await LoginApi.login({ userId: "CLEANWATER", password: "gktneh12!@" }); + window.location.href = "/pages/dashboard"; + } catch (err) { + console.error("로그인 실패", err); + } } - } - doLogin(); - } else { - router.push("/pages/dashboard"); - } + doLogin(); + } else router.push("/pages/dashboard"); + } else router.push("/pages/dashboard"); }, []); } \ No newline at end of file diff --git a/apps/fo/app/pages/(map-layout)/map/page.tsx b/apps/fo/app/pages/(map-layout)/map/page.tsx index 86b6ee508..1e3789bfe 100644 --- a/apps/fo/app/pages/(map-layout)/map/page.tsx +++ b/apps/fo/app/pages/(map-layout)/map/page.tsx @@ -295,6 +295,7 @@ const Map = () => { return (
{el[head.id]}: (head.id === 'flowRate' && index === 5)?{el[head.id]}: (head.id === 'flowRate' && index === 11)?{el[head.id]}: - (head.id === 'groundWaterAvg'||head.id === 'groundWaterSummer'||head.id === 'groundWaterWinter')&&(index === 0)?{el[head.id]}: + (head.id === 'groundWaterAvg'||head.id === 'groundWaterSummer'||head.id === 'groundWaterWinter')&&(index === 0)?{Number.isNaN(el[head.id]) ? "" : el[head.id]}: (head.id === 'groundWaterAvg'||head.id === 'groundWaterSummer'||head.id === 'groundWaterWinter')&&(index === 7)?{el[head.id]}: (head.id === 'rdi'&&index === 0)?{el[head.id]}: (head.id === 'rdi'&&index === 7)?{el[head.id]}: diff --git a/apps/fo/app/pages/(with-layout)/dashboard/page.tsx b/apps/fo/app/pages/(with-layout)/dashboard/page.tsx index a6cf99008..88d723d0b 100644 --- a/apps/fo/app/pages/(with-layout)/dashboard/page.tsx +++ b/apps/fo/app/pages/(with-layout)/dashboard/page.tsx @@ -6,7 +6,10 @@ "use client" import { useFoStore } from "@/app/store"; -import { ContentsDiv, downloadExcel, downloadMultipleExcel, filePickedBlobArray, MainDiv, SelectBox, UITab, type SelectDataType } from "@common/business_components"; +import { + ContentsDiv, downloadExcelFormat, downloadMultipleExcel, filePickedBlobArrayFormat, MainDiv + , SelectBox, UITab, type SelectDataType +} from "@common/business_components"; import { Button, TabsContent } from "@common/components/ui"; import { useEffect, useState } from "react"; import DashBoardFclty from "../dashboardMain/page"; @@ -19,7 +22,7 @@ import { roundNumber } from "@/app/components/common/DataForm"; const tabList = [ { value: 'facilityInfo', label: '시설정보 및 운영현황' }, - { value: 'mainGraph', label: '주요그래프' }, + { value: 'mainGraph', label: '주요그래프' }, { value: 'rainManage', label: '강우일 운영현황' }, //{ value: 'drainageEquipment', label: '배수설비 정비현황' } ]; @@ -40,34 +43,30 @@ export const generateYearDates = (searchYear: string) => { return dates; } +type FacilityType = { + bscFctCd: string; + bscFctNm: string; + fcltyCpc: number | ''; + trtFrfNm: string | ''; + efflWqltStndrdNm: string | ''; +} + export default function DashBoard() { // 입력값 상태 관리 - const { Common, initializeCommonData,searchMainGraph, searchRainManage, searchOperationStatus, OperationStatus, RainManage, MainGraph, PlanFclty } = useFoStore((state) => state); + const { Common, initializeCommonData, searchMainGraph, searchRainManage, searchOperationStatus + , OperationStatus, RainManage, MainGraph, PlanFclty } = useFoStore((state) => state); const [activeTab, setActiveTab] = useState('facilityInfo'); - const [filteredSigunData, setFilteredSigunData] = useState([{ text: "전체", val: '0' }]); const [sidoValue, setSidoValue] = useState('0'); const [sigunValue, setSigunValue] = useState('0'); - const { - isInit, - selectSearchYear, - btnClass, - selectSidoData, - selectSigunData, - } = Common; - - const { - planFcltyList - } = PlanFclty; - - const { - operationalStatusList - } = OperationStatus; - - const { - excelList - } = RainManage; - + const { isInit, selectSearchYear, btnClass, selectSidoData, selectSigunData, } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { planFcltyList } = PlanFclty; + const { operationalStatusList } = OperationStatus; + const { excelList } = RainManage; const { chart_inflowRain, chart_bodRain, @@ -79,10 +78,10 @@ export default function DashBoard() { } = MainGraph; /******************선택된 데이터******************/ - // 연도 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || '0'); + + const [searchYear, setSearchYear] = useState(defaultSearchYear); // 연도 // 시설명 - const [fcltyList, setFcltyList] = useState<{bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | ''}>({ + const [fcltyList, setFcltyList] = useState({ bscFctCd: '11000SW001R', bscFctNm: '난지', fcltyCpc: 860000, @@ -90,7 +89,7 @@ export default function DashBoard() { efflWqltStndrdNm: '500톤 이상(Ⅲ지역)' }); - const [searchFcltyList, setSearchFcltyList] = useState<{bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | ''}>({ + const [searchFcltyList, setSearchFcltyList] = useState({ bscFctCd: '', bscFctNm: '', fcltyCpc: '', @@ -111,12 +110,12 @@ export default function DashBoard() { } }, [isInit, initializeCommonData]); - const handleFacilityChange = (facility: { bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | '' }) => { + const handleFacilityChange = (facility: FacilityType) => { setFcltyList(facility); }; const onSelectValue = (val: string, type: string) => { - switch(type) { + switch (type) { case 'sido': setSidoValue(val); initializeSigunData(val); @@ -143,13 +142,11 @@ export default function DashBoard() { } } - const excelDownload = () => { + const excelDownload = async () => { if (!fcltyList.bscFctCd) return; - + switch (activeTab) { - case 'facilityInfo': - searchOperationStatus(false, searchYear, fcltyList.bscFctCd).then(() => {setTimeout(() => {makeExcelData();}, 200);}); - + case 'facilityInfo': // 시설정보 및 운영현황 const makeExcelData = async () => { fetch(`/mainDashBoard.xlsx`, {}).then(async (response) => { if (!response.ok) { @@ -161,7 +158,7 @@ export default function DashBoard() { const excelData1 = planFcltyList.map((item) => { return [ item.item, - item.provisionalApproval?Number(item.provisionalApproval.toString().replaceAll(',',''))?Number(item.provisionalApproval.toString().replaceAll(',','')):item.provisionalApproval:'', + item.provisionalApproval ? Number(item.provisionalApproval.toString().replaceAll(',', '')) ? Number(item.provisionalApproval.toString().replaceAll(',', '')) : item.provisionalApproval : '', item.now, item.stepOne, item.stepTwo, @@ -172,25 +169,25 @@ export default function DashBoard() { // operationalStatusList를 2차원 배열로 변환 const excelData2 = operationalStatusList.map((item, index) => { - console.log(item.yearAvg+' '+item.yearMax+' '+item.yearMin); + console.log(item.yearAvg + ' ' + item.yearMax + ' ' + item.yearMin); const baseData = [ - Number(item.yearAvg?.toString().replaceAll(',','')), - Number(item.yearMax?.toString().replaceAll(',','')), - Number(item.yearMin?.toString().replaceAll(',','')), - Number(item.summerAll?.toString().replaceAll(',','')), - Number(item.summerThree?.toString().replaceAll(',','')), - Number(item.summerRain?.toString().replaceAll(',','')), - Number(item.winterAll?.toString().replaceAll(',','')), - Number(item.winterSunnyAvg?.toString().replaceAll(',','')), - Number(item.winterSunnyMax?.toString().replaceAll(',','')), - Number(item.winterSunnyMin?.toString().replaceAll(',','')), - Number(item.groundWaterAvg?.toString().replaceAll(',','').replaceAll('%','')), - Number(item.groundWaterSummer?.toString().replaceAll(',','').replaceAll('%','')), - Number(item.groundWaterWinter?.toString().replaceAll(',','').replaceAll('%','')) + Number(item.yearAvg?.toString().replaceAll(',', '')), + Number(item.yearMax?.toString().replaceAll(',', '')), + Number(item.yearMin?.toString().replaceAll(',', '')), + Number(item.summerAll?.toString().replaceAll(',', '')), + Number(item.summerThree?.toString().replaceAll(',', '')), + Number(item.summerRain?.toString().replaceAll(',', '')), + Number(item.winterAll?.toString().replaceAll(',', '')), + Number(item.winterSunnyAvg?.toString().replaceAll(',', '')), + Number(item.winterSunnyMax?.toString().replaceAll(',', '')), + Number(item.winterSunnyMin?.toString().replaceAll(',', '')), + Number(item.groundWaterAvg?.toString().replaceAll(',', '').replaceAll('%', '')), + Number(item.groundWaterSummer?.toString().replaceAll(',', '').replaceAll('%', '')), + Number(item.groundWaterWinter?.toString().replaceAll(',', '').replaceAll('%', '')) ]; - + // index가 0일 때만 rdi와 rdiRain 추가 - return index === 0 ? [...baseData, item.rdi&&item.rdi.toString().trim()!=''?Number(item.rdi.toString().replaceAll(',','')):'', item.rdiRain&&item.rdiRain.toString().trim()!=''?Number(item.rdiRain.toString().replaceAll(',','')):''] : baseData; + return index === 0 ? [...baseData, item.rdi && item.rdi.toString().trim() != '' ? Number(item.rdi.toString().replaceAll(',', '')) : '', item.rdiRain && item.rdiRain.toString().trim() != '' ? Number(item.rdiRain.toString().replaceAll(',', '')) : ''] : baseData; }); const sheetParameter = [ @@ -199,48 +196,55 @@ export default function DashBoard() { headerNum: 3, headerRange: 'A1:G2', sheetName: '운영실태', - startCol: 'A' + startCol: 'A', + decimalRange0: 'B3:G2,B9:B11', + decimalRange1: 'B5:G5', }, { data: excelData2, headerNum: 4, headerRange: 'A1:Q3', sheetName: '운영실태', - startCol: 'C' + startCol: 'C', + decimalRange0: 'C4:O8', + decimalRange1: 'C9:L10,M11:O11', + decimalRange3: 'C11:L16', } ]; // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 - await filePickedBlobArray(blobRet, '시설정보및운영현황', sheetParameter); + await filePickedBlobArrayFormat(blobRet, '시설정보및운영현황', sheetParameter); }); }; + await searchOperationStatus(false, searchYear, fcltyList.bscFctCd); + await makeExcelData(); break; - case 'mainGraph': + case 'mainGraph': // 주요그래프 searchMainGraph(searchYear, fcltyList.bscFctCd); const yearData = generateYearDates(searchYear); - const yearValueData : any = []; + const yearValueData: any = []; - for (const i in yearData){ + for (const i in yearData) { const rowArray: string[] = []; rowArray.push(yearData[i] ?? ""); //강수량 - rowArray.push(roundNumber(chart_inflowRain.filter(item=>{return yearData[i] === item.label})[0]?.barVal0?.toString(), 1) || ''); + rowArray.push(roundNumber(chart_inflowRain.filter(item => { return yearData[i] === item.label })[0]?.barVal0?.toString(), 1) || ''); //유입하수량 - rowArray.push(roundNumber(chart_inflowRain.filter(item=>{return yearData[i] === item.label})[0]?.lineVal1?.toString(), 0) || ''); + rowArray.push(roundNumber(chart_inflowRain.filter(item => { return yearData[i] === item.label })[0]?.lineVal1?.toString(), 0) || ''); //시설용량 - rowArray.push(roundNumber(chart_inflowRain.filter(item=>{return yearData[i] === item.label})[0]?.lineVal0?.toString(), 0) || ''); + rowArray.push(roundNumber(chart_inflowRain.filter(item => { return yearData[i] === item.label })[0]?.lineVal0?.toString(), 0) || ''); //유입BOD - rowArray.push(roundNumber(chart_bodRain.filter(item=>{return yearData[i] === item.label})[0]?.lineVal0?.toString(), 1) || ''); + rowArray.push(roundNumber(chart_bodRain.filter(item => { return yearData[i] === item.label })[0]?.lineVal0?.toString(), 1) || ''); //유입T-P - rowArray.push(roundNumber(chart_inflTp.filter(item=>{return yearData[i] === item.label})[0]?.val?.toString(), 3) || ''); + rowArray.push(roundNumber(chart_inflTp.filter(item => { return yearData[i] === item.label })[0]?.val?.toString(), 3) || ''); //총인유입T-P - rowArray.push(roundNumber(chart_chongInTp.filter(item=>{return yearData[i] === item.label})[0]?.val?.toString(), 3) || ''); + rowArray.push(roundNumber(chart_chongInTp.filter(item => { return yearData[i] === item.label })[0]?.val?.toString(), 3) || ''); //방류T-P - rowArray.push(roundNumber(chart_efflTp.filter(item=>{return yearData[i] === item.label})[0]?.val?.toString(), 3) || ''); + rowArray.push(roundNumber(chart_efflTp.filter(item => { return yearData[i] === item.label })[0]?.val?.toString(), 3) || ''); //유입T-N - rowArray.push(roundNumber(chart_inflTn.filter(item=>{return yearData[i] === item.label})[0]?.val?.toString(), 3) || ''); + rowArray.push(roundNumber(chart_inflTn.filter(item => { return yearData[i] === item.label })[0]?.val?.toString(), 3) || ''); //방류T-N - rowArray.push(roundNumber(chart_efflTn.filter(item=>{return yearData[i] === item.label})[0]?.val?.toString(), 3) || ''); + rowArray.push(roundNumber(chart_efflTn.filter(item => { return yearData[i] === item.label })[0]?.val?.toString(), 3) || ''); yearValueData.push( rowArray @@ -250,74 +254,66 @@ export default function DashBoard() { const sheetParameter = [ { - data: [['날짜', '강수량', '유입하수량', '지하수유입량', '유입BOD', '유입T-P','총인유입T-P', '방류T-P', '유입T-N', '방류T-N'],...yearValueData], + data: [['날짜', '강수량', '유입하수량', '지하수유입량', '유입BOD', '유입T-P', '총인유입T-P', '방류T-P', '유입T-N', '방류T-N'], ...yearValueData], headerNum: 1, headerRange: 'A1:J1', sheetName: '주요그래프' } - /* { - data: [['날짜', '강수량'],...chart_inflowRain.map((item) => [item.label, roundNumber(item.barVal0, 1)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '강수량' - }, - { - data: [['날짜', '유입하수량'],...chart_inflowRain.map((item) => [item.label, roundNumber(item.lineVal1, 0)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '유입하수량' - }, - { - data: [['날짜', '시설용량'],...chart_inflowRain.map((item) => [item.label, roundNumber(item.lineVal0, 0)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '시설용량' - }, - { - data: [['날짜', '유입BOD'],...chart_bodRain.map((item) => [item.label, roundNumber(item.lineVal0, 1)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '유입BOD' - }, - { - data: [['날짜', '유입T-P'],...chart_inflTp.map((item) => [item.label, roundNumber(item.val, 3)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '유입T-P' - }, - { - data: [['날짜', '총인유입T-P'],...chart_chongInTp.map((item) => [item.label, roundNumber(item.val, 3)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '총인유입T-P' - }, - { - data: [['날짜', '방류T-P'],...chart_efflTp.map((item) => [item.label, roundNumber(item.val, 3)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '방류T-P' - }, - { - data: [['날짜', '유입T-N'],...chart_inflTn.map((item) => [item.label, roundNumber(item.val, 3)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '유입T-N' - }, - { - data: [['날짜', '방류T-N'],...chart_efflTn.map((item) => [item.label, roundNumber(item.val, 3)])], - headerNum: 1, - headerRange: 'A1:B1', - sheetName: '방류T-N' - } */ ]; downloadMultipleExcel('주요그래프', sheetParameter); break; - case 'rainManage': - searchRainManage(searchYear, fcltyList.bscFctCd, true); + case 'rainManage': // 강우일 운영현황 + await searchRainManage(searchYear, fcltyList.bscFctCd, true); if (excelList && excelList.length > 0) { - downloadExcel({ data: excelList as unknown as JSON[], fileName: '강우일 운영현황', sheetNameArray: ['강우일 운영현황'], headerRange: 'A1:T1' }); + const toNumber = (value: unknown) => { + if (value === null || value === undefined || value === '') return ''; + const parsed = typeof value === "number" ? value : Number(String(value).replace(/,/g, "")); + return Number.isFinite(parsed) ? parsed : ''; + }; + + const numericKeys = [ + "강우조건비율", + "일수", + "유입유량", + "시설용량대비", + "설계유입수질BOD", + "설계유입수질TOC", + "설계유입수질SS", + "설계유입수질TN", + "설계유입수질TP", + "유입수질BOD", + "유입수질TOC", + "유입수질SS", + "유입수질TN", + "유입수질TP", + "방류수질BOD", + "방류수질TOC", + "방류수질SS", + "방류수질TN", + "방류수질TP", + ]; + + const excelData = excelList.map((row) => { + const nextRow = { ...row } as Record; + numericKeys.forEach((key) => { + if (key in nextRow) { + nextRow[key] = toNumber(nextRow[key]); + } + }); + return nextRow; + }); + + downloadExcelFormat({ + data: excelData as unknown as JSON[], + fileName: '강우일 운영현황', + sheetNameArray: ['강우일 운영현황'], + headerRange: 'A1:T1', + decimalArray0: [2,3], + decimalArray1: [1,4,5,6,7,10,11,12,15,16,17], + decimalArray3: [8,9,13,14,18,19], + }); } break; // Add other cases for different tabs as needed @@ -342,7 +338,7 @@ export default function DashBoard() { trtFrfNm: '표준활성슬러지법, MLE, A2O', efflWqltStndrdNm: '500톤 이상(Ⅲ지역)' }); - setSearchYear(selectSearchYear[0]?.val || '0'); + setSearchYear(defaultSearchYear); searchFcltyList.bscFctNm = '난지'; setSidoValue('0'); @@ -351,7 +347,7 @@ export default function DashBoard() { const searchVal = () => { setSearchFcltyList(fcltyList); - if(searchFcltyList.bscFctCd) { + if (searchFcltyList.bscFctCd) { searchOperationStatus(false, searchYear, fcltyList.bscFctCd); } } @@ -361,11 +357,11 @@ export default function DashBoard() {
- setSearchYear(val.toString()) } value={searchYear} selectClass={undefined}/> - onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined}/> - onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined}/> + setSearchYear(val.toString())} value={searchYear} selectClass={undefined} /> + onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined} /> + onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined} />
- +
@@ -385,13 +381,13 @@ export default function DashBoard() { - + - + - + diff --git a/apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx b/apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx index e084bf04d..55e6275c8 100644 --- a/apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx +++ b/apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx @@ -15,27 +15,13 @@ export type facilityInfoProps = { fcltyList?: { bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string, efflWqltStndrdNm: string }[]; }; -export default function RainManageTab({searchYear = '', fcltyList = []}: facilityInfoProps) { - - const { - Common, - searchRainManage, - RainManage - } = useFoStore((state) => state); +export default function RainManageTab({ searchYear = '', fcltyList = [] }: facilityInfoProps) { + const { Common, searchRainManage, RainManage } = useFoStore((state) => state); const [hasSearched, setHasSearched] = useState(false); - // 이전 시설코드 저장 - const [prevFacilityCode, setPrevFacilityCode] = useState(''); - - const { - isInit, - } = Common; - - const { - upHeadListRainManage, - rainManageList, - } = RainManage; - + const [prevFacilityCode, setPrevFacilityCode] = useState(''); // 이전 시설코드 저장 + const { isInit, } = Common; + const { upHeadListRainManage, rainManageList, } = RainManage; const tableData = fcltyList; useEffect(() => { @@ -50,17 +36,17 @@ export default function RainManageTab({searchYear = '', fcltyList = []}: facilit useEffect(() => { setHasSearched(false); }, [searchYear]); - + return (
- {}} + onPageChange={() => { }} widthFixed={true} > diff --git a/apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx b/apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx index 1bd8a6c89..811f1c0ff 100644 --- a/apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx @@ -9,7 +9,7 @@ import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import { roundNumber } from "@/app/components/common/DataForm"; import { useFoStore } from "@/app/store"; import type { PrecipitationPropsDetailProps } from "@/app/store/facilityPrecipitation"; -import { DatePickerRange, downloadExcel, UiTable } from "@common/business_components/ui"; +import { DatePickerRange, downloadExcelHeaders, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Table, TableCell, TableHead, TableRow } from "@common/components/ui"; import { useEffect, useRef, useState } from "react"; @@ -45,7 +45,7 @@ export default function PrecipitationModal(props: PrecipitationModalProps) { const standYear = parseInt(props.searchYear); return [{ start: new Date(standYear, 0, 1), - end: new Date(standYear, 11, 31) + end: new Date(standYear, 11, 31) //월의 index는 0부터 시작 }]; }; @@ -112,7 +112,6 @@ export default function PrecipitationModal(props: PrecipitationModalProps) { const getTableData = () => { const totals = calculateTotals(); totals.infoRnDay = roundNumber(totals.infoRnDay, 1); - return [ { bscFctCd: "", @@ -157,19 +156,26 @@ export default function PrecipitationModal(props: PrecipitationModalProps) { const excelDownload = () => { const totData = getTableData(); - const excelDownList: any[] = []; + + // 헤더 순서를 명시적으로 정의 + const headers = ["날짜", "강수량(mm/일)", "강우일(일)", "강우영향일(일)"] as const; + + const excelDownList: Record[] = []; if(totData.length > 0){ totData.forEach((item) => { - excelDownList.push({ - "날짜": item.rainDt, - "강수량(mm/일)": roundNumber(item.infoRnDay, 1), - "강우일(일)": roundNumber(item.rainDay, 0), - "강우영향일(일)": roundNumber(item.rainEffectDay, 0), - }); + // 순서를 보장하기 위해 헤더 순서대로 객체 생성 + const row: Record = {}; + row[headers[0]] = item.rainDt; + row[headers[1]] = roundNumber(item.infoRnDay, 1); + row[headers[2]] = roundNumber(item.rainDay, 0); + row[headers[3]] = roundNumber(item.rainEffectDay, 0); + excelDownList.push(row); }); } + if (excelDownList && excelDownList.length > 0) { - downloadExcel({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.searchYear+'년 '+props.fcltyName + ' 강수현황', sheetNameArray: ['지하수유입량'], headerRange: 'A1:D1' }); + downloadExcelHeaders({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.searchYear+'년 '+props.fcltyName + ' 강수현황' + , sheetNameArray: ['지하수유입량'], headerRange: 'A1:D1', headers }); } else if (totData.length == 0) { alert('다운받을 데이터가 없습니다.'); } diff --git a/apps/fo/app/pages/(with-layout)/facilityPrecipitation/page.tsx b/apps/fo/app/pages/(with-layout)/facilityPrecipitation/page.tsx index 0e46505e0..cbc9861e0 100644 --- a/apps/fo/app/pages/(with-layout)/facilityPrecipitation/page.tsx +++ b/apps/fo/app/pages/(with-layout)/facilityPrecipitation/page.tsx @@ -5,11 +5,11 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import PrecipitationModal from "./modal/page"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; import UnderComment from "@/app/components/common/UnderComment"; @@ -22,6 +22,11 @@ export default function FacilityPrecipitation() { ,initializeCommonData ,searchFacilityPrecipitationList } = useFoStore((state) => state) || { processAreaList: { processAreaList: [] }}; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { isInit, @@ -37,6 +42,11 @@ export default function FacilityPrecipitation() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { labelArray, } = FacilityPrecipitationSearch; @@ -45,7 +55,6 @@ export default function FacilityPrecipitation() { upHeadList1, upHeadList2, upHeadList3, - excelList, facilityPrecipitation, facilityPrecipitationCount, isLoading, @@ -90,8 +99,9 @@ export default function FacilityPrecipitation() { // 시설상태 const [operationValue, setOperationValue] = useState("0"); // 조회기간 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); - + const [searchYear, setSearchYear] = useState(defaultSearchYear); + // 엑셀다운로드 트리거 + const [isExcelDownClick, setIsExcelDownClick] = useState(false); /** * 페이지 함수 */ @@ -102,7 +112,7 @@ export default function FacilityPrecipitation() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYear(selectSearchYear[0]?.val || "0"); + setSearchYear(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -111,7 +121,7 @@ export default function FacilityPrecipitation() { input.value = ""; } } - searchFacilityPrecipitationList(1, 10, selectSearchYear[0]?.val || "0"); + searchFacilityPrecipitationList(1, 10, defaultSearchYear); } // 모달 닫기 @@ -134,20 +144,9 @@ export default function FacilityPrecipitation() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchFacilityPrecipitationList(1, 10, selectSearchYear[0]?.val || "0"); + searchFacilityPrecipitationList(1, 10, defaultSearchYear); } - }, [isInit, initializeCommonData, searchFacilityPrecipitationList, selectSearchYear]); - - useEffect(() => { - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: '시설별 강수현황', - sheetNameArray: ['시설별 강수현황'], - headerRange: 'A1:N1' - }); - } - }, [excelList]); + }, [isInit, initializeCommonData, searchFacilityPrecipitationList, defaultSearchYear]); useEffect(() => { if (facilityPrecipitation.length > 0) { @@ -159,7 +158,7 @@ export default function FacilityPrecipitation() { * 콜백함수 */ // 조회 - const searchFacilityPrecipitation = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchFacilityPrecipitation = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { let nameCd, partStr switch (partValue) { case 'R': @@ -177,9 +176,9 @@ export default function FacilityPrecipitation() { break; } if (partStr.length > 1 || operationValue!== '0' || fcltCpct !== '0' ||nameCd ||(fcltyName.trim() !== "" && fcltyName.trim() !== null)) { - searchFacilityPrecipitationList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + await searchFacilityPrecipitationList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); } else { - searchFacilityPrecipitationList(pageNo, pageCnt, searchYear); + await searchFacilityPrecipitationList(pageNo, pageCnt, searchYear); } }, [operationValue, fcltyName, searchFacilityPrecipitationList, searchYear, fcltCpct]); @@ -190,9 +189,43 @@ export default function FacilityPrecipitation() { }, [upHeadList1, upHeadList2, upHeadList3]); // 엑셀다운로드 - const excelDownload = useCallback((searchReturn: ReturnType) => { - searchFacilityPrecipitation(searchReturn, 1, 10000); - }, [searchFacilityPrecipitation]); + const excelDownload = useCallback(async (searchReturn: ReturnType) => { + if (isExcelDownClick) return; + // if (ref.current.partValue === 'A') { // 행정별 + // if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + // alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'R') { // 유역별 + // if (!ref.current.regionValue || ref.current.regionValue === '0') { + // alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'G') { // 환경청별 + // if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + // alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } + setIsExcelDownClick(true); + try { + await searchFacilityPrecipitation(searchReturn, 1, 10000); + const latestExcelList = store.getState().FacilityPrecipitation.excelList || []; + if (latestExcelList.length > 0) { + downloadExcelFormat({ + data: latestExcelList as unknown as JSON[], + fileName: '시설별 강수현황', + sheetNameArray: ['시설별 강수현황'], + headerRange: 'A1:N1', + decimalArray0: [9, 11, 12, 13], + }); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFacilityPrecipitation, store]); // 페이지 변경 const handlePageChange = useCallback((page: number) => { @@ -205,14 +238,16 @@ export default function FacilityPrecipitation() { return ( - - <ContentsDiv> + <ContentsDiv> <SearchBaseForm leftChildren={<> - <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> + <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} + onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> <SearchInput className={"col-span-3"} label={labelArray[4] as string} textValue={fcltyName} setText={setFcltyName} /> - <SelectBox className="col-span-3" label={labelArray[6] as string} selectArray={selectSearchYear} onSelectValue={(val) => setSearchYear(val.toString())} value={searchYear} selectClass={undefined} /> - <SelectBox className="col-span-3" label={labelArray[5] as string} selectArray={selectOperationData} onSelectValue={(val) => setOperationValue(val.toString())} value={operationValue} selectClass={undefined} /> + <SelectBox className="col-span-3" label={labelArray[6] as string} selectArray={selectSearchYear} + onSelectValue={(val) => setSearchYear(val.toString())} value={searchYear} selectClass={undefined} /> + <SelectBox className="col-span-3" label={labelArray[5] as string} selectArray={selectOperationData} + onSelectValue={(val) => setOperationValue(val.toString())} value={operationValue} selectClass={undefined} /> </>} rightChildren={undefined} selectSidoData={selectSidoData} @@ -224,18 +259,22 @@ export default function FacilityPrecipitation() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={false}/> - <ListDiv> - <LoadingOverlay isLoading={isLoading} /> - <div className="h-full w-full overflow-x-scroll min-[1200px]:overflow-x-hidden"> - <div className="w-[120%] min-[1200px]:w-[100%]"> - <UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} headName={"시설별 강수현황"} tableData={mainList} headlist={upheadList} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> + <ListDiv> + <LoadingOverlay isLoading={isLoading} /> + <div className="h-full w-full overflow-x-scroll min-[1200px]:overflow-x-hidden"> + <div className="w-[120%] min-[1200px]:w-[100%]"> + <UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} + headName={"시설별 강수현황"} + tableData={mainList} headlist={upheadList} pageSize={10} total={total} + resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> + </div> + <UnderComment divHeight={"3"} commentArray={["강우일은 3mm 이상만 해당 / 강우영향일은 강우일 및 강우일 후 연속 2일간으로 정의"]} /> </div> - <UnderComment divHeight={"3"} commentArray={["강우일은 3mm 이상만 해당 / 강우영향일은 강우일 및 강우일 후 연속 2일간으로 정의"]} /> - </div> - </ListDiv> - </ContentsDiv> + </ListDiv> + </ContentsDiv> {selectedRow !== null && ( <UiModal body={<PrecipitationModal searchYear={searchYear} fcltyName={fcltyNm} facilityCd={fcltyCd} detailList={gridListPrecipitationDetail}></PrecipitationModal>} @@ -248,4 +287,4 @@ export default function FacilityPrecipitation() { )} </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/facilityWaterQlty/modal/page.tsx b/apps/fo/app/pages/(with-layout)/facilityWaterQlty/modal/page.tsx index c10fd50cf..9c6d5360a 100644 --- a/apps/fo/app/pages/(with-layout)/facilityWaterQlty/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/facilityWaterQlty/modal/page.tsx @@ -25,10 +25,10 @@ const commentArrayRain = [ "강우량 3mm 이상만 날과 강우영향일(강우일+2일)의 날" ]; -type ModalProps ={ +type ModalProps = { fcltyName: string; - facilityCd : string; - searchYear : string; + facilityCd: string; + searchYear: string; } const tabList1 = [ @@ -50,15 +50,13 @@ const tabList3 = [ { value: 'tab_term_winter_chongin', label: '동절기' } ]; -export default function WaterQualityModal({fcltyName='', facilityCd='', searchYear=new Date().getFullYear().toString()} : ModalProps) { +export default function WaterQualityModal({ fcltyName = '', facilityCd = '', searchYear = new Date().getFullYear().toString() }: ModalProps) { const [tabName, setTabName] = useState<string>('tab_num1'); - - // 그리드 관련 - const {FacilityWaterQltyModal, - searchFacilityWaterQltyListHist, - Common - } = useFoStore((state) => state); - const {btnClass, COD_YEAR} = Common; + // 날짜 필터링 관련 상태 + const [copyYearList, setCopyYearList] = useState<FacilityWaterQltyProps[]>([]); + const [chongInWaterQltyYearList, setChongInWaterQltyYearList] = useState<FacilityWaterQltyProps[]>([]); + const { FacilityWaterQltyModal, searchFacilityWaterQltyListHist, Common } = useFoStore((state) => state); + const { btnClass, COD_YEAR } = Common; const { upHeadListModalYear, upHeadListModalYearCod, @@ -71,7 +69,17 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe const prevExcelListRef = useRef<modalExcelType[] | null>(null); const prevChoinginExcelListRef = useRef<modalChoinginExcelType[] | null>(null); - + + useEffect(() => { + searchFacilityWaterQltyListHist(searchYear, facilityCd); + }, [searchYear, facilityCd, searchFacilityWaterQltyListHist]); + + useEffect(() => { + const filteredYearData = setFilterAfterData(facilityWaterQltyYearList); + setCopyYearList(filteredYearData as FacilityWaterQltyProps[]); + setChongInWaterQltyYearList(facilityWaterQltyYearList); + }, [facilityWaterQltyYearList]); + // 필터링 된 후 최대, 최소, 평균 값 계산 const setFilterAfterData = (filteredData: FacilityWaterQltyProps[]) => { // 쉼표가 포함된 문자열을 숫자로 변환하는 함수 @@ -100,7 +108,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let maxNoProcessInflAmnt = 0; let maxInfoRnDay = 0; - + // 초기값 설정 let minEfflWqltBod = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltBod) : 0; let minEfflWqltTn = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltTn) : 0; @@ -108,14 +116,14 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let minefflWqltToc = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltToc) : 0; let minefflWqltCod = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltCod) : 0; let minEfflWqltSs = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltSs) : 0; - + let mininflAssctBfrWqltBod = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltBod) : 0; let mininflAssctBfrWqltTn = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltTn) : 0; let minInflAssctBfrWqltTp = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltTp) : 0; let mininflAssctBfrWqltToc = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltToc) : 0; let mininflAssctBfrWqltCod = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltCod) : 0; let mininflAssctBfrWqltSs = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctBfrWqltSs) : 0; - + let minTpInflWqltBod = 99999999; let minTpInflWqltTp = 99999999; let minNoProcessInflAmnt = 99999999; @@ -143,27 +151,27 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let noProcessInflAmntCnt = 0; let avgInfoRnDay = 0; - + filteredData.forEach((item: FacilityWaterQltyProps) => { const efflWqltBod = parseNumber(item.efflWqltBod); const efflWqltTn = parseNumber(item.efflWqltTn); const efflWqltTp = parseNumber(item.efflWqltTp); const efflWqltToc = parseNumber(item.efflWqltToc); - const efflWqltCod = parseNumber(item.efflWqltCod?item.efflWqltCod.replaceAll("(","").replaceAll(")",""):0); + const efflWqltCod = parseNumber(item.efflWqltCod ? item.efflWqltCod.replaceAll("(", "").replaceAll(")", "") : 0); const efflWqltSs = parseNumber(item.efflWqltSs); const inflAssctBfrWqltBod = parseNumber(item.inflAssctBfrWqltBod); const inflAssctBfrWqltTn = parseNumber(item.inflAssctBfrWqltTn); const inflAssctBfrWqltTp = parseNumber(item.inflAssctBfrWqltTp); const inflAssctBfrWqltToc = parseNumber(item.inflAssctBfrWqltToc); - const inflAssctBfrWqltCod = parseNumber(item.inflAssctBfrWqltCod?item.inflAssctBfrWqltCod.replaceAll("(","").replaceAll(")",""):0); + const inflAssctBfrWqltCod = parseNumber(item.inflAssctBfrWqltCod ? item.inflAssctBfrWqltCod.replaceAll("(", "").replaceAll(")", "") : 0); const inflAssctBfrWqltSs = parseNumber(item.inflAssctBfrWqltSs); const tpInflWqltBod = parseNumber(item.tpInflWqltBod); const tpInflWqltTp = parseNumber(item.tpInflWqltTp); - if(item.tpInflWqltBod != null && item.tpInflWqltBod.trim() != '' && item.tpInflWqltBod != undefined ){ + if (item.tpInflWqltBod != null && item.tpInflWqltBod.trim() != '' && item.tpInflWqltBod != undefined) { tpInflWqltBodCnt += 1; } - if(item.tpInflWqltTp != null && item.tpInflWqltTp.trim() != '' && item.tpInflWqltTp != undefined ){ + if (item.tpInflWqltTp != null && item.tpInflWqltTp.trim() != '' && item.tpInflWqltTp != undefined) { tpInflWqltTpCnt += 1; } @@ -175,7 +183,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe maxefflWqltToc = Math.max(maxefflWqltToc, efflWqltToc); maxefflWqltCod = Math.max(maxefflWqltCod, efflWqltCod); maxEfflWqltSs = Math.max(maxEfflWqltSs, efflWqltSs); - + maxinflAssctBfrWqltBod = Math.max(maxinflAssctBfrWqltBod, inflAssctBfrWqltBod); maxinflAssctBfrWqltTn = Math.max(maxinflAssctBfrWqltTn, inflAssctBfrWqltTn); maxInflAssctBfrWqltTp = Math.max(maxInflAssctBfrWqltTp, inflAssctBfrWqltTp); @@ -202,11 +210,11 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe mininflAssctBfrWqltCod = Math.min(mininflAssctBfrWqltToc, inflAssctBfrWqltCod); mininflAssctBfrWqltSs = Math.min(mininflAssctBfrWqltSs, inflAssctBfrWqltSs); - if(item.tpInflWqltBod != null && item.tpInflWqltBod.trim() != '' && item.tpInflWqltBod != undefined ){ + if (item.tpInflWqltBod != null && item.tpInflWqltBod.trim() != '' && item.tpInflWqltBod != undefined) { minTpInflWqltBod = Math.min(minTpInflWqltBod, tpInflWqltBod); avgTpInflWqltBod += tpInflWqltBod; } - if(item.tpInflWqltTp != null && item.tpInflWqltTp.trim() != '' && item.tpInflWqltTp != undefined ){ + if (item.tpInflWqltTp != null && item.tpInflWqltTp.trim() != '' && item.tpInflWqltTp != undefined) { minTpInflWqltTp = Math.min(minTpInflWqltTp, tpInflWqltTp); avgTpInflWqltTp += tpInflWqltTp; } @@ -227,12 +235,12 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe avginflAssctBfrWqltCod += inflAssctBfrWqltCod; avginflAssctBfrWqltSs += inflAssctBfrWqltSs; - if(item.noProcessInflAmnt && item.noProcessInflAmnt.toString().trim() != '' && item.noProcessInflAmnt != undefined){ - const noProcessInflAmnt = Number(item.noProcessInflAmnt.toString().replaceAll(',','')); + if (item.noProcessInflAmnt && item.noProcessInflAmnt.toString().trim() != '' && item.noProcessInflAmnt != undefined) { + const noProcessInflAmnt = Number(item.noProcessInflAmnt.toString().replaceAll(',', '')); avgNoProcessInflAmnt += noProcessInflAmnt; maxNoProcessInflAmnt = Math.max(maxNoProcessInflAmnt, noProcessInflAmnt); minNoProcessInflAmnt = Math.min(minNoProcessInflAmnt, noProcessInflAmnt); - noProcessInflAmntCnt +=1; + noProcessInflAmntCnt += 1; } avgInfoRnDay += infoRnDay; @@ -241,18 +249,18 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe const newFilteredData = [ { msrDt: '평균', - efflWqltBod: filteredData.length==0 || avgEfflWqltBod===0?'0.0':roundNumber((avgEfflWqltBod / filteredData.length), 1), - efflWqltToc: filteredData.length==0 || avgefflWqltToc===0?'0.0':roundNumber((avgefflWqltToc / filteredData.length), 1), - efflWqltCod: filteredData.length==0 || avgefflWqltCod===0?"(0.0)":"("+roundNumber((avgefflWqltCod / filteredData.length), 1)+")", - efflWqltSs: filteredData.length==0 || avgEfflWqltSs===0?'0.0':roundNumber((avgEfflWqltSs / filteredData.length), 1), - efflWqltTn: filteredData.length==0 || avgEfflWqltTn ===0?'0.000':roundNumber((avgEfflWqltTn / filteredData.length), 3), - efflWqltTp: filteredData.length==0 || avgEfflWqltTp ===0?'0.000':roundNumber((avgEfflWqltTp / filteredData.length), 3), - inflAssctBfrWqltBod: filteredData.length==0 || avginflAssctBfrWqltBod===0?'0.0':roundNumber((avginflAssctBfrWqltBod / filteredData.length), 1), - inflAssctBfrWqltTn: filteredData.length==0 || avginflAssctBfrWqltTn===0?'0.000':roundNumber((avginflAssctBfrWqltTn / filteredData.length), 3), - inflAssctBfrWqltTp: filteredData.length==0 || avgInflAssctBfrWqltTp===0?'0.000':roundNumber((avgInflAssctBfrWqltTp / filteredData.length), 3), - inflAssctBfrWqltToc: filteredData.length==0 || avginflAssctBfrWqltToc===0?'0.0':roundNumber((avginflAssctBfrWqltToc / filteredData.length), 1), - inflAssctBfrWqltCod: filteredData.length==0 || avginflAssctBfrWqltCod===0?'(0.0)':"("+roundNumber((avginflAssctBfrWqltCod / filteredData.length), 1)+")", - inflAssctBfrWqltSs: filteredData.length==0 || avginflAssctBfrWqltSs===0?'0.0':roundNumber((avginflAssctBfrWqltSs / filteredData.length), 1), + efflWqltBod: filteredData.length == 0 || avgEfflWqltBod === 0 ? '0.0' : roundNumber((avgEfflWqltBod / filteredData.length), 1), + efflWqltToc: filteredData.length == 0 || avgefflWqltToc === 0 ? '0.0' : roundNumber((avgefflWqltToc / filteredData.length), 1), + efflWqltCod: filteredData.length == 0 || avgefflWqltCod === 0 ? "(0.0)" : "(" + roundNumber((avgefflWqltCod / filteredData.length), 1) + ")", + efflWqltSs: filteredData.length == 0 || avgEfflWqltSs === 0 ? '0.0' : roundNumber((avgEfflWqltSs / filteredData.length), 1), + efflWqltTn: filteredData.length == 0 || avgEfflWqltTn === 0 ? '0.000' : roundNumber((avgEfflWqltTn / filteredData.length), 3), + efflWqltTp: filteredData.length == 0 || avgEfflWqltTp === 0 ? '0.000' : roundNumber((avgEfflWqltTp / filteredData.length), 3), + inflAssctBfrWqltBod: filteredData.length == 0 || avginflAssctBfrWqltBod === 0 ? '0.0' : roundNumber((avginflAssctBfrWqltBod / filteredData.length), 1), + inflAssctBfrWqltTn: filteredData.length == 0 || avginflAssctBfrWqltTn === 0 ? '0.000' : roundNumber((avginflAssctBfrWqltTn / filteredData.length), 3), + inflAssctBfrWqltTp: filteredData.length == 0 || avgInflAssctBfrWqltTp === 0 ? '0.000' : roundNumber((avgInflAssctBfrWqltTp / filteredData.length), 3), + inflAssctBfrWqltToc: filteredData.length == 0 || avginflAssctBfrWqltToc === 0 ? '0.0' : roundNumber((avginflAssctBfrWqltToc / filteredData.length), 1), + inflAssctBfrWqltCod: filteredData.length == 0 || avginflAssctBfrWqltCod === 0 ? '(0.0)' : "(" + roundNumber((avginflAssctBfrWqltCod / filteredData.length), 1) + ")", + inflAssctBfrWqltSs: filteredData.length == 0 || avginflAssctBfrWqltSs === 0 ? '0.0' : roundNumber((avginflAssctBfrWqltSs / filteredData.length), 1), bscFctCd: "", bscFctNm: "", lclgvrnmntNm: "", @@ -260,25 +268,25 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe sggNm: "", addr: "", fctCpc: 0, - noProcessInflAmnt : roundNumber(avgNoProcessInflAmnt / noProcessInflAmntCnt, 0), - infoRnDay: filteredData.length==0 || avgInfoRnDay===0?'0.0':roundNumber(avgInfoRnDay / filteredData.length, 1), - tpInflWqltBod: filteredData.length==0 || avgTpInflWqltBod ===0 ?'0.0':roundNumber(avgTpInflWqltBod / tpInflWqltBodCnt, 1), - tpInflWqltTp: filteredData.length==0 || avgTpInflWqltTp === 0?'0.000':roundNumber(avgTpInflWqltTp / tpInflWqltTpCnt, 3) + noProcessInflAmnt: roundNumber(avgNoProcessInflAmnt / noProcessInflAmntCnt, 0), + infoRnDay: filteredData.length == 0 || avgInfoRnDay === 0 ? '0.0' : roundNumber(avgInfoRnDay / filteredData.length, 1), + tpInflWqltBod: filteredData.length == 0 || avgTpInflWqltBod === 0 ? '0.0' : roundNumber(avgTpInflWqltBod / tpInflWqltBodCnt, 1), + tpInflWqltTp: filteredData.length == 0 || avgTpInflWqltTp === 0 ? '0.000' : roundNumber(avgTpInflWqltTp / tpInflWqltTpCnt, 3) }, { - msrDt : '최대', - efflWqltBod : Math.round(maxEfflWqltBod * 10) / 10?roundNumber(maxEfflWqltBod, 1):'0.0', - efflWqltToc : Math.round(maxefflWqltToc * 10) / 10?roundNumber(maxefflWqltToc, 1):'0.0', - efflWqltCod : Math.round(maxefflWqltCod * 10) / 10?"("+roundNumber(maxefflWqltCod, 1)+")":'(0.0)', - efflWqltSs : Math.round(maxEfflWqltSs * 10) / 10?roundNumber(maxEfflWqltSs, 1):'0.0', - efflWqltTn : Math.round(maxEfflWqltTn * 1000) / 1000?roundNumber(maxEfflWqltTn, 3):'0.000', - efflWqltTp : Math.round(maxEfflWqltTp * 1000) / 1000?roundNumber(maxEfflWqltTp, 3):'0.000', - inflAssctBfrWqltBod : Math.round(maxinflAssctBfrWqltBod * 10) / 10?roundNumber(maxinflAssctBfrWqltBod, 1):'0.0', - inflAssctBfrWqltTn : Math.round(maxinflAssctBfrWqltTn * 1000) / 1000?roundNumber(maxinflAssctBfrWqltTn, 3):'0.000', - inflAssctBfrWqltTp : Math.round(maxInflAssctBfrWqltTp * 1000) / 1000?roundNumber(maxInflAssctBfrWqltTp, 3):'0.000', - inflAssctBfrWqltToc : Math.round(maxinflAssctBfrWqltToc * 10) / 10?roundNumber(maxinflAssctBfrWqltToc, 1):'0.0', - inflAssctBfrWqltCod : Math.round(maxinflAssctBfrWqltCod * 10) / 10?"("+roundNumber(maxinflAssctBfrWqltCod, 1)+")":'(0.0)', - inflAssctBfrWqltSs : Math.round(maxinflAssctBfrWqltSs * 10) / 10?roundNumber(maxinflAssctBfrWqltSs, 1):'0.0', + msrDt: '최대', + efflWqltBod: Math.round(maxEfflWqltBod * 10) / 10 ? roundNumber(maxEfflWqltBod, 1) : '0.0', + efflWqltToc: Math.round(maxefflWqltToc * 10) / 10 ? roundNumber(maxefflWqltToc, 1) : '0.0', + efflWqltCod: Math.round(maxefflWqltCod * 10) / 10 ? "(" + roundNumber(maxefflWqltCod, 1) + ")" : '(0.0)', + efflWqltSs: Math.round(maxEfflWqltSs * 10) / 10 ? roundNumber(maxEfflWqltSs, 1) : '0.0', + efflWqltTn: Math.round(maxEfflWqltTn * 1000) / 1000 ? roundNumber(maxEfflWqltTn, 3) : '0.000', + efflWqltTp: Math.round(maxEfflWqltTp * 1000) / 1000 ? roundNumber(maxEfflWqltTp, 3) : '0.000', + inflAssctBfrWqltBod: Math.round(maxinflAssctBfrWqltBod * 10) / 10 ? roundNumber(maxinflAssctBfrWqltBod, 1) : '0.0', + inflAssctBfrWqltTn: Math.round(maxinflAssctBfrWqltTn * 1000) / 1000 ? roundNumber(maxinflAssctBfrWqltTn, 3) : '0.000', + inflAssctBfrWqltTp: Math.round(maxInflAssctBfrWqltTp * 1000) / 1000 ? roundNumber(maxInflAssctBfrWqltTp, 3) : '0.000', + inflAssctBfrWqltToc: Math.round(maxinflAssctBfrWqltToc * 10) / 10 ? roundNumber(maxinflAssctBfrWqltToc, 1) : '0.0', + inflAssctBfrWqltCod: Math.round(maxinflAssctBfrWqltCod * 10) / 10 ? "(" + roundNumber(maxinflAssctBfrWqltCod, 1) + ")" : '(0.0)', + inflAssctBfrWqltSs: Math.round(maxinflAssctBfrWqltSs * 10) / 10 ? roundNumber(maxinflAssctBfrWqltSs, 1) : '0.0', bscFctCd: "", bscFctNm: "", lclgvrnmntNm: "", @@ -286,25 +294,25 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe sggNm: "", addr: "", fctCpc: 0, - noProcessInflAmnt : roundNumber(maxNoProcessInflAmnt, 0), - infoRnDay: Math.round(maxInfoRnDay * 10) / 10?roundNumber(maxInfoRnDay, 1):'0.0', - tpInflWqltBod: Math.round(maxTpInflWqltBod * 10) / 10?roundNumber(maxTpInflWqltBod, 1):'0.0', - tpInflWqltTp: Math.round(maxTpInflWqltTp * 1000) / 1000?roundNumber(maxTpInflWqltTp, 3):'0.000' + noProcessInflAmnt: roundNumber(maxNoProcessInflAmnt, 0), + infoRnDay: Math.round(maxInfoRnDay * 10) / 10 ? roundNumber(maxInfoRnDay, 1) : '0.0', + tpInflWqltBod: Math.round(maxTpInflWqltBod * 10) / 10 ? roundNumber(maxTpInflWqltBod, 1) : '0.0', + tpInflWqltTp: Math.round(maxTpInflWqltTp * 1000) / 1000 ? roundNumber(maxTpInflWqltTp, 3) : '0.000' }, { - msrDt : '최소', - efflWqltBod : Math.round(minEfflWqltBod * 10) / 10?roundNumber(minEfflWqltBod, 1) : '0.0', - efflWqltToc : Math.round(minefflWqltToc * 10) / 10?roundNumber(minefflWqltToc, 1) : '0.0', - efflWqltCod : Math.round(minefflWqltCod * 10) / 10?"("+roundNumber(minefflWqltCod, 1)+")" : '(0.0)', - efflWqltSs : Math.round(minEfflWqltSs * 10) / 10?roundNumber(minEfflWqltSs, 1) : '0.0', - efflWqltTn : Math.round(minEfflWqltTn * 1000) / 1000?roundNumber(minEfflWqltTn, 3) : '0.000', - efflWqltTp : Math.round(minEfflWqltTp * 1000) / 1000?roundNumber(minEfflWqltTp, 3) : '0.000', - inflAssctBfrWqltBod : Math.round(mininflAssctBfrWqltBod * 10) / 10?roundNumber(mininflAssctBfrWqltBod, 1) : '0.0', - inflAssctBfrWqltTn : Math.round(mininflAssctBfrWqltTn * 1000) / 1000?roundNumber(mininflAssctBfrWqltTn, 3) : '0.000', - inflAssctBfrWqltTp : Math.round(minInflAssctBfrWqltTp * 1000) / 1000?roundNumber(minInflAssctBfrWqltTp, 3) : '0.000', - inflAssctBfrWqltToc : Math.round(mininflAssctBfrWqltToc * 10) / 10?roundNumber(mininflAssctBfrWqltToc, 1) : '0.0', - inflAssctBfrWqltCod : Math.round(mininflAssctBfrWqltCod * 10) / 10?"("+roundNumber(mininflAssctBfrWqltCod, 1)+")" : '(0.0)', - inflAssctBfrWqltSs : Math.round(mininflAssctBfrWqltSs * 10) / 10?roundNumber(mininflAssctBfrWqltSs, 1) : '0.0', + msrDt: '최소', + efflWqltBod: Math.round(minEfflWqltBod * 10) / 10 ? roundNumber(minEfflWqltBod, 1) : '0.0', + efflWqltToc: Math.round(minefflWqltToc * 10) / 10 ? roundNumber(minefflWqltToc, 1) : '0.0', + efflWqltCod: Math.round(minefflWqltCod * 10) / 10 ? "(" + roundNumber(minefflWqltCod, 1) + ")" : '(0.0)', + efflWqltSs: Math.round(minEfflWqltSs * 10) / 10 ? roundNumber(minEfflWqltSs, 1) : '0.0', + efflWqltTn: Math.round(minEfflWqltTn * 1000) / 1000 ? roundNumber(minEfflWqltTn, 3) : '0.000', + efflWqltTp: Math.round(minEfflWqltTp * 1000) / 1000 ? roundNumber(minEfflWqltTp, 3) : '0.000', + inflAssctBfrWqltBod: Math.round(mininflAssctBfrWqltBod * 10) / 10 ? roundNumber(mininflAssctBfrWqltBod, 1) : '0.0', + inflAssctBfrWqltTn: Math.round(mininflAssctBfrWqltTn * 1000) / 1000 ? roundNumber(mininflAssctBfrWqltTn, 3) : '0.000', + inflAssctBfrWqltTp: Math.round(minInflAssctBfrWqltTp * 1000) / 1000 ? roundNumber(minInflAssctBfrWqltTp, 3) : '0.000', + inflAssctBfrWqltToc: Math.round(mininflAssctBfrWqltToc * 10) / 10 ? roundNumber(mininflAssctBfrWqltToc, 1) : '0.0', + inflAssctBfrWqltCod: Math.round(mininflAssctBfrWqltCod * 10) / 10 ? "(" + roundNumber(mininflAssctBfrWqltCod, 1) + ")" : '(0.0)', + inflAssctBfrWqltSs: Math.round(mininflAssctBfrWqltSs * 10) / 10 ? roundNumber(mininflAssctBfrWqltSs, 1) : '0.0', bscFctCd: "", bscFctNm: "", lclgvrnmntNm: "", @@ -312,10 +320,10 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe sggNm: "", addr: "", fctCpc: 0, - noProcessInflAmnt : roundNumber(minNoProcessInflAmnt, 0), - infoRnDay: Math.round(minInfoRnDay * 10) / 10?'0.0':roundNumber(minInfoRnDay, 1), - tpInflWqltBod: Math.round(minTpInflWqltBod * 10) / 10 && minTpInflWqltBod != 99999999?roundNumber(minTpInflWqltBod, 1):'0.0', - tpInflWqltTp: Math.round(minTpInflWqltTp * 1000) / 1000 && minTpInflWqltTp != 99999999?roundNumber(minTpInflWqltTp, 3):'0.000' + noProcessInflAmnt: roundNumber(minNoProcessInflAmnt, 0), + infoRnDay: Math.round(minInfoRnDay * 10) / 10 ? roundNumber(minInfoRnDay, 1) : '0.0', + tpInflWqltBod: Math.round(minTpInflWqltBod * 10) / 10 && minTpInflWqltBod != 99999999 ? roundNumber(minTpInflWqltBod, 1) : '0.0', + tpInflWqltTp: Math.round(minTpInflWqltTp * 1000) / 1000 && minTpInflWqltTp != 99999999 ? roundNumber(minTpInflWqltTp, 3) : '0.000' }, ...filteredData ]; @@ -323,20 +331,6 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe return newFilteredData; } - // 날짜 필터링 관련 상태 - const [copyYearList, setCopyYearList] = useState<FacilityWaterQltyProps[]>([]); - const [chongInWaterQltyYearList, setChongInWaterQltyYearList] = useState<FacilityWaterQltyProps[]>([]); - - useEffect(() => { - searchFacilityWaterQltyListHist(searchYear, facilityCd); - }, [searchYear, facilityCd, searchFacilityWaterQltyListHist]); - - useEffect(() => { - const filteredYearData = setFilterAfterData(facilityWaterQltyYearList); - setCopyYearList(filteredYearData as FacilityWaterQltyProps[]); - setChongInWaterQltyYearList(filteredYearData as FacilityWaterQltyProps[]); - }, [facilityWaterQltyYearList]); - const yesterDay = new Date(); yesterDay.setDate(yesterDay.getDate() - 1); const yesterDayStr = yesterDay.toISOString().split('T')[0]; @@ -350,14 +344,14 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe const filterData = useCallback((returnType: ReturnType<FacilityWaterQltyProps>) => { const filteredData = setFilterAfterData(returnType.filteredList); - setCopyYearList(filteredData as FacilityWaterQltyProps[]); + setCopyYearList(filteredData as FacilityWaterQltyProps[]); // 처리시설/총인설비 수질현황 }, []); const excelDownload = () => { - - if(tabName === 'tab_num1'){ + + if (tabName === 'tab_num1') { //처리시설 수질현황 const excelDownList: modalExcelType[] = []; - if(copyYearList.length > 0){ + if (copyYearList.length > 0) { copyYearList.forEach((item) => { excelDownList.push({ 날짜: item.msrDt, @@ -371,44 +365,48 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe "유출SS(mg/L)": CommaToNumber(item.efflWqltSs), "유출TN(mg/L)": CommaToNumber(item.efflWqltTn), "유출TP(mg/L)": CommaToNumber(item.efflWqltTp), - "강우량(mm/일)": CommaToNumber(item.infoRnDay?item.infoRnDay.toString():"0.0"), + "강우량(mm/일)": CommaToNumber(item.infoRnDay ? item.infoRnDay.toString() : "0.0"), }); }); } if (excelDownList && excelDownList.length > 0) { downloadExcelFormat( - { data: JSON.parse(JSON.stringify(excelDownList)) - , fileName: searchYear+'년 '+fcltyName + ' 수질현황' + { + data: JSON.parse(JSON.stringify(excelDownList)) + , fileName: searchYear + '년 ' + fcltyName + ' 수질현황' , sheetNameArray: [fcltyName + ' 수질현황'] , headerRange: 'A1:L1' - , decimalArray1: [1,2,3,6,7,8,11] - , decimalArray3: [4,5,9,10] + , decimalArray1: [1, 2, 3, 6, 7, 8, 11] + , decimalArray3: [4, 5, 9, 10] }); } else if (prevExcelListRef.current && prevExcelListRef.current.length > 0) { alert('다운받을 데이터가 없습니다.'); } prevExcelListRef.current = excelDownList; - }else{ + } else { // 총인설비 수질현황 const excelDownList: modalChoinginExcelType[] = []; - if(chongInWaterQltyYearList.length > 0){ - chongInWaterQltyYearList.forEach((item) => { + // if (chongInWaterQltyYearList.length > 0) { + // chongInWaterQltyYearList.forEach((item) => { + if (copyYearList.length > 0) { + copyYearList.forEach((item) => { excelDownList.push({ 날짜: item.msrDt, "유입BOD(mg/L)": CommaToNumber(item.tpInflWqltBod), "유입TP(mg/L)": CommaToNumber(item.tpInflWqltTp), "유출BOD(mg/L)": CommaToNumber(item.efflWqltBod), - "유출TP(mg/L)": CommaToNumber(item.efflWqltTn), + "유출TP(mg/L)": CommaToNumber(item.efflWqltTp), }); }); } if (excelDownList && excelDownList.length > 0) { downloadExcelFormat( - { data: JSON.parse(JSON.stringify(excelDownList)) - , fileName: searchYear+'년 '+fcltyName + ' 총인설비수질현황' + { + data: JSON.parse(JSON.stringify(excelDownList)) + , fileName: searchYear + '년 ' + fcltyName + ' 총인설비수질현황' , sheetNameArray: [fcltyName + ' 총인설비수질현황'] , headerRange: 'A1:E1' - , decimalArray1: [1,3] - , decimalArray3: [2,4] + , decimalArray1: [1, 3] + , decimalArray3: [2, 4] }); } else if (prevChoinginExcelListRef.current && prevChoinginExcelListRef.current.length > 0) { alert('다운받을 데이터가 없습니다.'); @@ -424,168 +422,127 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe return ( <> <UITab tabList={tabList1} defaultValue="tab_num1" onValueChange={(value) => setTabName(value)}> - <Button className={btnClass} style={{float: 'right'}} color="green" onClick={excelDownload}> + <Button className={btnClass} style={{ float: 'right' }} color="green" onClick={excelDownload}> {"엑셀다운로드"} </Button> {/* 처리시설 수질현황 */} <TabsContent value="tab_num1"> <UITab tabList={tabList2} defaultValue="tab_term_year"> - <TabsContent value="tab_term_year"> + <TabsContent value="tab_term_year">{/* 연간 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={facilityWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName={"tab_term_year"} - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_year" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[350px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={Number(searchYear)<COD_YEAR?upHeadListModalYearCod:upHeadListModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[310px]" - boldArray={[0,1,2]} + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={Number(searchYear) < COD_YEAR ? upHeadListModalYearCod : upHeadListModalYear} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[310px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalYear} layoutNum={3} /> </UiTable> </div> - <div className="card-header"> + <div className="card-header border-t mt-1"> <h3 className="text-2xl font-medium text-label">수질 TMS</h3> - {/*<h3 className="text-2xl font-medium text-label text-right">({msrmtDate?msrmtDate.substring(0,4)+'-'+msrmtDate.substring(4,6)+'-'+msrmtDate.substring(6,8):yesterDayStr})</h3> */} + <h3 className="text-2xl font-medium text-label text-right"> + ({msrmtDate ? msrmtDate.substring(0, 4) + '-' + msrmtDate.substring(4, 6) + '-' + msrmtDate.substring(6, 8) : yesterDayStr}) + </h3> </div> <div className="overflow-y-auto h-[250px]"> - <UiTable - headName={undefined} - tableData={tmsList} - headlist={upHeadListModalTms} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - boldArray={[0,1,2]} + <UiTable + headName={undefined} + tableData={tmsList} + headlist={upHeadListModalTms} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalTms} layoutNum={4} /> </UiTable> - {msrmtDate != yesterDayStr?.replaceAll('-','') ? ( - <UnderComment commentArray={["전날 데이터 결측으로 인해 가장 최근 데이터 표출함"]} divHeight="0"/> + {msrmtDate != yesterDayStr?.replaceAll('-', '') ? ( + <UnderComment commentArray={["전날 데이터 결측으로 인해 가장 최근 데이터 표출함"]} divHeight="0" /> ) : null} </div> </TabsContent> - <TabsContent value="tab_term_summer"> + <TabsContent value="tab_term_summer">{/* 하절기 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={facilityWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_summer" - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_summer" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[500px]"> - <UiTable + <UiTable headName={undefined} tableData={copyYearList} - headlist={Number(searchYear)<COD_YEAR?upHeadListModalYearCod:upHeadListModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => { } } - className="h-[500px]" - boldArray={[0,1,2]} + headlist={Number(searchYear) < COD_YEAR ? upHeadListModalYearCod : upHeadListModalYear} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalYear} layoutNum={3} /> </UiTable> </div> </TabsContent> - <TabsContent value="tab_term_winter"> + <TabsContent value="tab_term_winter">{/* 동절기 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={facilityWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_winter" - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_winter" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[500px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={Number(searchYear)<COD_YEAR?upHeadListModalYearCod:upHeadListModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={Number(searchYear) < COD_YEAR ? upHeadListModalYearCod : upHeadListModalYear} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalYear} layoutNum={3} /> </UiTable> </div> </TabsContent> - <TabsContent value="tab_term_chuncheon"> + <TabsContent value="tab_term_chuncheon">{/* 청천시 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={facilityWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_chuncheon" - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_chuncheon" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[500px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={Number(searchYear)<COD_YEAR?upHeadListModalYearCod:upHeadListModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={Number(searchYear) < COD_YEAR ? upHeadListModalYearCod : upHeadListModalYear} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalYear} layoutNum={3} /> </UiTable> </div> - <UnderComment commentArray={commentArrayChungchun} divHeight="10"/> + <UnderComment commentArray={commentArrayChungchun} divHeight="10" /> </TabsContent> - <TabsContent value="tab_term_rain"> + <TabsContent value="tab_term_rain">{/* 강우시 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={facilityWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_rain" - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_rain" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[500px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={Number(searchYear)<COD_YEAR?upHeadListModalYearCod:upHeadListModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={Number(searchYear) < COD_YEAR ? upHeadListModalYearCod : upHeadListModalYear} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalYear} layoutNum={3} /> </UiTable> </div> - <UnderComment commentArray={commentArrayRain} divHeight="10"/> + <UnderComment commentArray={commentArrayRain} divHeight="10" /> </TabsContent> </UITab> </TabsContent> @@ -593,81 +550,57 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe {/* 총인설비 수질현황 */} <TabsContent value="tab_num2" > <UITab tabList={tabList3} defaultValue="tab_term_year_chongin"> - <TabsContent value="tab_term_year_chongin"> + <TabsContent value="tab_term_year_chongin">{/* 연간 */} <FacilityTermModalHeader<FacilityWaterQltyProps> searchList={chongInWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_year" - searchYear={searchYear} - ref={ref} - queryClick={filterData} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_year" + searchYear={searchYear} ref={ref} queryClick={filterData} /> <div className="overflow-y-auto h-[550px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={upHeadListModalChongIn} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} - > - <HeadMakeColSpan upHeadList={upHeadListModalChongIn} layoutNum={5} /> - </UiTable> - </div> - </TabsContent> - - <TabsContent value="tab_term_summer_chongin"> - <FacilityTermModalHeader<FacilityWaterQltyProps> - searchList={chongInWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_summer" - searchYear={searchYear} - ref={ref} - queryClick={filterData} - /> - <div className="overflow-y-auto h-[550px]"> - <UiTable - headName={undefined} - tableData={copyYearList} - headlist={upHeadListModalChongIn} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} - > - <HeadMakeColSpan upHeadList={upHeadListModalChongIn} layoutNum={5} /> - </UiTable> - </div> - </TabsContent> - - <TabsContent value="tab_term_winter_chongin"> - <FacilityTermModalHeader<FacilityWaterQltyProps> - searchList={chongInWaterQltyYearList} - fcltyCd={facilityCd} - fcltyName={fcltyName} - tabName="tab_term_winter" - searchYear={searchYear} - ref={ref} - queryClick={filterData} - /> - <div className="overflow-y-auto h-[550px]"> - <UiTable - headName={undefined} - tableData={copyYearList} + <UiTable + headName={undefined} + tableData={copyYearList} headlist={upHeadListModalChongIn} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[500px]" - boldArray={[0,1,2]} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} + > + <HeadMakeColSpan upHeadList={upHeadListModalChongIn} layoutNum={5} /> + </UiTable> + </div> + </TabsContent> + + <TabsContent value="tab_term_summer_chongin">{/* 하절기 */} + <FacilityTermModalHeader<FacilityWaterQltyProps> + searchList={chongInWaterQltyYearList} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_summer" + searchYear={searchYear} ref={ref} queryClick={filterData} + /> + <div className="overflow-y-auto h-[550px]"> + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={upHeadListModalChongIn} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} + > + <HeadMakeColSpan upHeadList={upHeadListModalChongIn} layoutNum={5} /> + </UiTable> + </div> + </TabsContent> + + <TabsContent value="tab_term_winter_chongin">{/* 동절기 */} + <FacilityTermModalHeader<FacilityWaterQltyProps> + searchList={chongInWaterQltyYearList} + fcltyCd={facilityCd} fcltyName={fcltyName} tabName="tab_term_winter" + searchYear={searchYear} ref={ref} queryClick={filterData} + /> + <div className="overflow-y-auto h-[550px]"> + <UiTable + headName={undefined} + tableData={copyYearList} + headlist={upHeadListModalChongIn} + pageSize={0} total={0} cellClick={undefined} + onPageChange={() => { }} className="h-[500px]" boldArray={[0, 1, 2]} > <HeadMakeColSpan upHeadList={upHeadListModalChongIn} layoutNum={5} /> </UiTable> @@ -678,4 +611,4 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe </UITab> </> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/facilityWaterQlty/page.tsx b/apps/fo/app/pages/(with-layout)/facilityWaterQlty/page.tsx index 885ee2728..43c0d674d 100644 --- a/apps/fo/app/pages/(with-layout)/facilityWaterQlty/page.tsx +++ b/apps/fo/app/pages/(with-layout)/facilityWaterQlty/page.tsx @@ -6,13 +6,13 @@ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; import type { FacilityWaterQltyProps } from "@/app/store/facilityWaterQlty"; -import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import WaterQualityModal from "./modal/page"; export default function ProcessFacilitySearch() { @@ -21,9 +21,14 @@ export default function ProcessFacilitySearch() { , initializeCommonData , searchFacilityWaterQltyList } = useFoStore((state) => state) || { FacilityWaterQltyList: { facilityWaterQltyList: [], facilityWaterQltyCount: 0 } }; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { - isInit, + isInit, selectUpdownData, selectOperationData, selectSidoData, @@ -36,6 +41,11 @@ export default function ProcessFacilitySearch() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { mainLabelArray, upHeadList1, @@ -43,13 +53,13 @@ export default function ProcessFacilitySearch() { upHeadList3, facilityWaterQltyList, facilityWaterQltyCount, - excelList, isLoading, } = FacilityWaterQltyMain; const mainList: FacilityWaterQltyProps[] = facilityWaterQltyList || []; const [initTrigger, setInitTrigger] = useState<boolean>(false); - const [excelTrigger, setExcelTrigger] = useState<boolean>(false); + // 엑셀다운로드 트리거 + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); // 그리드 관련 const [resetPage, setResetPage] = useState<boolean>(false); const searchInputRef = useRef<HTMLDivElement & HTMLInputElement>(null); @@ -57,7 +67,7 @@ export default function ProcessFacilitySearch() { const [upheadList, setUpheadList] = useState<TableUpperProps[]>(upHeadList1); const [fcltyName, setFcltyName] = useState<string>(""); const [operationValue, setOperationValue] = useState<string>("0"); - const [searchYearValue, setSearchYearValue] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYearValue, setSearchYearValue] = useState<string>(defaultSearchYear); const [fcltCpct, setFcltCpct] = useState<string>("0"); // 모달 상태 추가 @@ -66,27 +76,27 @@ export default function ProcessFacilitySearch() { const [fcltyCd, setFcltyCd] = useState<string>(""); const [modalYear, setModalYear] = useState<string>(""); - const searchFcltyQltyList = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchFcltyQltyList = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt: number = 20) => { let nameCd, partStr; switch (partValue) { case 'R': if (regionValue !== '0') { - if(unitWaterShedValue==='0') nameCd = regionValue; - else if (middleRegionValue==='0') nameCd = unitWaterShedValue; + if (unitWaterShedValue === '0') nameCd = regionValue; + else if (middleRegionValue === '0') nameCd = unitWaterShedValue; else nameCd = middleRegionValue; } partStr = partValue break; default: - if(sidoValue !== '0') nameCd = ref.current.sidoValue!="0"?ref.current.sidoValue:sidoValue; - if(sigunValue !== '0') nameCd = sigunValue; - partStr = envPartValue !== '0' ? envPartValue:partValue; - break; + if (sidoValue !== '0') nameCd = ref.current.sidoValue != "0" ? ref.current.sidoValue : sidoValue; + if (sigunValue !== '0') nameCd = sigunValue; + partStr = envPartValue !== '0' ? envPartValue : partValue; + break; } - if(partStr ==='G'){ - searchFacilityWaterQltyList(pageNo, pageCnt, searchYearValue); - }else{ - searchFacilityWaterQltyList(pageNo, pageCnt, searchYearValue, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + if (partStr === 'G') { + await searchFacilityWaterQltyList(pageNo, pageCnt, searchYearValue); + } else { + await searchFacilityWaterQltyList(pageNo, pageCnt, searchYearValue, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); } }, [operationValue, fcltyName, searchFacilityWaterQltyList, searchYearValue, fcltCpct]); @@ -96,7 +106,7 @@ export default function ProcessFacilitySearch() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYearValue(selectSearchYear[0]?.val || "0"); + setSearchYearValue(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -138,42 +148,62 @@ export default function ProcessFacilitySearch() { useEffect(() => { if (!isInit) { - if(!initTrigger){ + if (!initTrigger) { initializeCommonData(); searchFcltyQltyList(ref.current, 1, 10); - setInitTrigger(true); + setInitTrigger(true); } } }, [isInit, initializeCommonData, searchFacilityWaterQltyList, searchFcltyQltyList, initTrigger]); - useEffect(() => { - if(!excelTrigger) return; - - if (excelList && excelList.length > 0) { - downloadExcelFormat( - { - data: excelList as unknown as JSON[], - fileName: '시설별 수질현황', - sheetNameArray: ['시설별 수질현황'], - headerRange: 'A1:T1', - decimalArray1:[10,11,12,15,16,17], - decimalArray3:[13,14,18,19] - } - ); - } - }, [excelList, excelTrigger]); - - const chkVal = useCallback((returntype:ReturnType) => { + const chkVal = useCallback((returntype: ReturnType) => { // setWidthSize(returntype.partValue=='A' ?"w-[120%]":"w-[130%]") - setUpheadList(returntype.partValue == 'R' ? upHeadList2 : returntype.partValue == 'G'?upHeadList3:upHeadList1); + setUpheadList(returntype.partValue == 'R' ? upHeadList2 : returntype.partValue == 'G' ? upHeadList3 : upHeadList1); setResetPage(true); }, [upHeadList1, upHeadList2, upHeadList3]); - const excelDownload = useCallback(() => { - setExcelTrigger(true); - searchFcltyQltyList(ref.current, 1, 10000); - }, [searchFcltyQltyList]); - + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + if (ref.current.partValue === 'A') { // 행정별 + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'R') { // 유역별 + if (!ref.current.regionValue || ref.current.regionValue === '0') { + alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'G') { // 환경청별 + if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + return; + } + } + setIsExcelDownClick(true); + try { + await searchFcltyQltyList(ref.current, 1, 10000); + const latestExcelList = store.getState().FacilityWaterQltyMain.excelList || []; + if (latestExcelList.length > 0) { + downloadExcelFormat( + { + data: latestExcelList as unknown as JSON[], + fileName: '시설별 수질현황', + sheetNameArray: ['시설별 수질현황'], + headerRange: 'A1:T1', + decimalArray0: [9], + decimalArray1: [10, 11, 12, 15, 16, 17 ], + decimalArray3: [13, 14, 18, 19] + } + ); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFcltyQltyList, store]); + const handlePageChange = useCallback((page: number) => { setResetPage(false); searchFcltyQltyList(ref.current, page, 10); @@ -182,7 +212,6 @@ export default function ProcessFacilitySearch() { const total = facilityWaterQltyCount || 1; return ( <MainDiv> - <Title title="시설별 수질현황" /> <ContentsDiv> <SearchBaseForm leftChildren={<> @@ -201,8 +230,9 @@ export default function ProcessFacilitySearch() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} - ref={ref} isAdmin={false} initSidoValue={"11"}/> + ref={ref} isAdmin={false} initSidoValue={"11"} /> <ListDiv> <LoadingOverlay isLoading={isLoading} /> <div className="h-full w-full overflow-x-scroll min-[1200px]:overflow-x-hidden"> @@ -215,7 +245,7 @@ export default function ProcessFacilitySearch() { {selectedRow !== null && ( <UiModal body={<WaterQualityModal fcltyName={fcltyNm} facilityCd={fcltyCd} searchYear={modalYear}></WaterQualityModal>} - bodyClass="sm:max-w-[90vw] w-[1300px] h-[920px]" + bodyClass="sm:max-w-[90vw] w-[1300px] h-[100dvh] max-h-[930px] overflow-y-auto" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} noHeaderYn="Y" @@ -224,4 +254,4 @@ export default function ProcessFacilitySearch() { )} </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/flowRate/modal/page.tsx b/apps/fo/app/pages/(with-layout)/flowRate/modal/page.tsx index 42512f802..0cffa775e 100644 --- a/apps/fo/app/pages/(with-layout)/flowRate/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/flowRate/modal/page.tsx @@ -8,7 +8,8 @@ import FacilityTermModalHeader from "@/app/components/common/FacilityTermModalHeader"; import { useFoStore } from "@/app/store"; import type { FlowRateModalInterface, modalExcelType } from "@/app/store/flowRate"; -import { downloadExcel, UITab, UiTable } from "@common/business_components/ui"; +import { dataFormSetting } from "@/app/store/flowRate"; +import { downloadExcelFormat, UITab, UiTable } from "@common/business_components/ui"; import { Button, TabsContent } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import type { ReturnType } from "../../../../components/common/FacilityTermModalHeader"; @@ -33,12 +34,10 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st const { btnClass } = Common; const [copyYearList, setCopyYearList] = useState<FlowRateModalInterface[]>([]); const [isLoading, setIsLoading] = useState(true); + const [activeTab, setActiveTab] = useState<string>("tab_term_year"); const prevExcelListRef = useRef<modalExcelType[] | null>(null); - const { - modalFlowRateListYear, - upHeadListFlowRateModalYear, - } = FlowRateModal; - + const { modalFlowRateListYear, upHeadListFlowRateModalYear, } = FlowRateModal; + const ref = useRef<ReturnType<FlowRateModalInterface>>({ startDate: new Date(), endDate: new Date(), @@ -52,11 +51,11 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st const filteredData = setFilterAfterData(returnType.filteredList as FlowRateModalInterface[]); setCopyYearList(filteredData); }, []); - + // 필터링 된 후 최대, 최소, 평균 값 계산 const setFilterAfterData = (filteredData: FlowRateModalInterface[]) => { if (!filteredData || filteredData.length === 0) return []; - + // 쉼표가 포함된 문자열을 숫자로 변환하는 함수 const parseNumber = (value: string | number | null | undefined): number => { if (value === null || value === undefined || value === '') return 0; @@ -79,7 +78,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st let minreturnAmnt = parseNumber(filteredData[0]?.returnAmnt); let minefflAmnt = parseNumber(filteredData[0]?.efflAmnt); let mininfoRnDay = parseNumber(filteredData[0]?.infoRnDay && filteredData[0]?.infoRnDay.toString().trim() !== 'x' ? filteredData[0]?.infoRnDay.toString().trim() : 0); - let minNoProcessInflAmnt = 99999999; + let minNoProcessInflAmnt = 99999999; // 미처리하수 추정량 let avgtotInflAmnt = 0; let avginflAmnt = 0; @@ -89,7 +88,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st let avginfoRnDay = 0; let avgNoProcessInflAmnt = 0; let noProcessInflAmntCnt = 0; - + filteredData.forEach((item: FlowRateModalInterface) => { const totInflAmnt = parseNumber(item.totInflAmnt); const inflAmnt = parseNumber(item.inflAmnt); @@ -119,24 +118,30 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st avgefflAmnt += efflAmnt; avginfoRnDay += infoRnDay; - if(item.noProcessInflAmnt && item.noProcessInflAmnt.toString().trim() != '' && item.noProcessInflAmnt != undefined){ - const noProcessInflAmnt = Number(item.noProcessInflAmnt.toString().replaceAll(',','')); + if (item.noProcessInflAmnt && item.noProcessInflAmnt.toString().trim() != '' && item.noProcessInflAmnt != undefined) { + const noProcessInflAmnt = Number(item.noProcessInflAmnt.toString().replaceAll(',', '')); avgNoProcessInflAmnt += noProcessInflAmnt; maxNoProcessInflAmnt = Math.max(maxNoProcessInflAmnt, noProcessInflAmnt); minNoProcessInflAmnt = Math.min(minNoProcessInflAmnt, noProcessInflAmnt); - noProcessInflAmntCnt +=1; + noProcessInflAmntCnt += 1; } }); + const formattedData = filteredData.map((item: FlowRateModalInterface) => { + const newItem = { ...item }; + dataFormSetting(newItem); + return newItem; + }); + const newFilteredData = [ { msrDt: '평균', - totInflAmnt: avgtotInflAmnt && avgtotInflAmnt!=0 ? roundUp((avgtotInflAmnt / filteredData.length), 0) : 0, - inflAmnt: avginflAmnt && avginflAmnt!=0 ? roundUp((avginflAmnt / filteredData.length), 0) : 0, - sumFlow: avgsumFlow && avgsumFlow!=0 ? roundUp((avgsumFlow / filteredData.length), 0) : 0, - returnAmnt: avgreturnAmnt && avgreturnAmnt!=0 ? roundUp((avgreturnAmnt / filteredData.length), 0) : 0, - efflAmnt: avgefflAmnt && avgefflAmnt!=0 ? roundUp((avgefflAmnt / filteredData.length), 0) : 0, - noProcessInflAmnt : roundNumber(avgNoProcessInflAmnt / noProcessInflAmntCnt, 0), + totInflAmnt: avgtotInflAmnt && avgtotInflAmnt != 0 ? roundUp((avgtotInflAmnt / filteredData.length), 0) : 0, + inflAmnt: avginflAmnt && avginflAmnt != 0 ? roundUp((avginflAmnt / filteredData.length), 0) : 0, + sumFlow: avgsumFlow && avgsumFlow != 0 ? roundUp((avgsumFlow / filteredData.length), 0) : 0, + returnAmnt: avgreturnAmnt && avgreturnAmnt != 0 ? roundUp((avgreturnAmnt / filteredData.length), 0) : 0, + efflAmnt: avgefflAmnt && avgefflAmnt != 0 ? roundUp((avgefflAmnt / filteredData.length), 0) : 0, + noProcessInflAmnt: roundNumber(avgNoProcessInflAmnt / noProcessInflAmntCnt, 0), infoRnDay: avginfoRnDay > 0 ? roundUp((avginfoRnDay / filteredData.length), 1) : '0.0', rainEffectDay: 0, }, @@ -147,7 +152,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st sumFlow: roundUp(maxsumFlow, 0), returnAmnt: roundUp(maxreturnAmnt, 0), efflAmnt: roundUp(maxefflAmnt, 0), - noProcessInflAmnt : roundNumber(maxNoProcessInflAmnt, 0), + noProcessInflAmnt: roundNumber(maxNoProcessInflAmnt, 0), infoRnDay: maxinfoRnDay > 0 ? roundUp(maxinfoRnDay, 1) : '0.0', rainEffectDay: 0, }, @@ -158,11 +163,11 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st sumFlow: roundUp(minsumFlow, 0), returnAmnt: roundUp(minreturnAmnt, 0), efflAmnt: roundUp(minefflAmnt, 0), - noProcessInflAmnt : roundNumber(minNoProcessInflAmnt, 0), + noProcessInflAmnt: roundNumber(minNoProcessInflAmnt, 0), infoRnDay: mininfoRnDay > 0 ? roundUp(mininfoRnDay, 1) : '0.0', rainEffectDay: 0, }, - ...filteredData + ...formattedData ]; return newFilteredData; } @@ -179,13 +184,9 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st fetchData(); }, [props.facilityCd, props.searchYear, searchFlowRateModalList]); - if (isLoading) { - return <div>로딩 중...</div>; - } - const excelDownload = () => { const excelDownList: modalExcelType[] = []; - if(copyYearList.length > 0){ + if (copyYearList.length > 0) { copyYearList.forEach((item) => { excelDownList.push({ "날짜": item.msrDt, @@ -200,20 +201,58 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st }); } if (excelDownList && excelDownList.length > 0) { - downloadExcel({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.searchYear+'년 '+props.fcltyName + ' 유량현황', sheetNameArray: [props.fcltyName + ' 유량현황'], headerRange: 'A1:H1' }); + downloadExcelFormat({ + data: JSON.parse(JSON.stringify(excelDownList)), + fileName: props.searchYear + '년 ' + props.fcltyName + ' 유량현황', + sheetNameArray: [props.fcltyName + ' 유량현황'], + headerRange: 'A1:H1', + decimalArray0: [1, 2, 3, 4, 5, 6], + decimalArray1: [0], + }); } else if (prevExcelListRef.current && prevExcelListRef.current.length > 0) { - alert('다운받을 데이터가 없습니다.'); + alert('조회된 결과가 없습니다.'); } prevExcelListRef.current = excelDownList; } + // const rainCellClassName = useCallback( + // (row: FlowRateModalInterface, headId: string, rowIndex: number) => { + // if (activeTab !== "tab_term_rain") return ""; + // if (headId !== "infoRnDay") return ""; + // if (rowIndex < 3) return ""; + + // const raw = row.infoRnDay; + // const value = typeof raw === "string" ? Number(raw.replace(/,/g, "").trim()) : Number(raw); + // if (!Number.isFinite(value)) return ""; + // return value > 3 ? "bg-red-50 font-semibold" : ""; + // }, + // [activeTab], + // ); + + const rainRowClassName = useCallback( + (row: FlowRateModalInterface, rowIndex: number) => { + if (activeTab !== "tab_term_rain") return ""; + if (rowIndex < 3) return ""; + + const raw = row.infoRnDay; + const value = typeof raw === "string" ? Number(raw.replace(/,/g, "").trim()) : Number(raw); + if (!Number.isFinite(value)) return ""; + return value > 3 ? "bg-slate-50" : ""; + }, + [activeTab], + ); + + if (isLoading) { + return <div>로딩 중...</div>; + } + return ( <> - <UITab tabList={tabList} defaultValue="tab_term_year"> - <Button className={btnClass} style={{float: 'right'}} color="green" onClick={excelDownload}> + <UITab tabList={tabList} defaultValue="tab_term_year" onValueChange={setActiveTab}> + <Button className={btnClass} style={{ float: 'right' }} color="green" onClick={excelDownload}> {"엑셀다운로드"} </Button> - <TabsContent value="tab_term_year"> + <TabsContent value="tab_term_year"> {/* 연간 */} <FacilityTermModalHeader<FlowRateModalInterface> searchList={modalFlowRateListYear} fcltyCd={props.facilityCd} @@ -224,23 +263,23 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st queryClick={filterData} /> <div className="h-[370px] overflow-y-auto overflow-x-hidden"> - <UiTable - headName="유량현황" - tableData={copyYearList} - headlist={upHeadListFlowRateModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[370px]" - boldArray={[0,1,2]} + <UiTable + headName="유량현황" + tableData={copyYearList} + headlist={upHeadListFlowRateModalYear} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={() => { }} + className="h-[370px]" + boldArray={[0, 1, 2]} /> </div> <div className="mt-20"> <UnderComment commentArray={[commentArray]} /> </div> </TabsContent> - <TabsContent value="tab_term_summer"> + <TabsContent value="tab_term_summer"> {/* 하절기 */} <FacilityTermModalHeader<FlowRateModalInterface> searchList={modalFlowRateListYear} fcltyCd={props.facilityCd} @@ -251,23 +290,23 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st queryClick={filterData} /> <div className="h-[370px] overflow-y-auto overflow-x-hidden"> - <UiTable - headName="유량현황" - tableData={copyYearList} - headlist={upHeadListFlowRateModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[370px]" - boldArray={[0,1,2]} + <UiTable + headName="유량현황" + tableData={copyYearList} + headlist={upHeadListFlowRateModalYear} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={() => { }} + className="h-[370px]" + boldArray={[0, 1, 2]} /> </div> <div className="mt-20"> <UnderComment commentArray={[commentArray]} /> </div> </TabsContent> - <TabsContent value="tab_term_winter"> + <TabsContent value="tab_term_winter"> {/* 동절기 */} <FacilityTermModalHeader<FlowRateModalInterface> searchList={modalFlowRateListYear} fcltyCd={props.facilityCd} @@ -278,23 +317,23 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st queryClick={filterData} /> <div className="h-[370px] overflow-y-auto overflow-x-hidden"> - <UiTable - headName="유량현황" - tableData={copyYearList} - headlist={upHeadListFlowRateModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[370px]" - boldArray={[0,1,2]} + <UiTable + headName="유량현황" + tableData={copyYearList} + headlist={upHeadListFlowRateModalYear} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={() => { }} + className="h-[370px]" + boldArray={[0, 1, 2]} /> </div> <div className="mt-20"> <UnderComment commentArray={[commentArray]} /> </div> </TabsContent> - <TabsContent value="tab_term_chuncheon"> + <TabsContent value="tab_term_chuncheon"> {/* 청천시 */} <FacilityTermModalHeader<FlowRateModalInterface> searchList={modalFlowRateListYear} fcltyCd={props.facilityCd} @@ -305,23 +344,23 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st queryClick={filterData} /> <div className="h-[370px] overflow-y-auto overflow-x-hidden"> - <UiTable - headName="유량현황" - tableData={copyYearList} - headlist={upHeadListFlowRateModalYear} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - className="h-[370px]" - boldArray={[0,1,2]} + <UiTable + headName="유량현황" + tableData={copyYearList} + headlist={upHeadListFlowRateModalYear} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={() => { }} + className="h-[370px]" + boldArray={[0, 1, 2]} /> </div> <div className="mt-20"> <UnderComment commentArray={[commentArray]} /> </div> </TabsContent> - <TabsContent value="tab_term_rain"> + <TabsContent value="tab_term_rain"> {/* 강우시 */} <FacilityTermModalHeader<FlowRateModalInterface> searchList={modalFlowRateListYear} fcltyCd={props.facilityCd} @@ -332,13 +371,15 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st queryClick={filterData} /> <div className="h-[370px] overflow-y-auto overflow-x-hidden"> - <UiTable headName="유량현황" tableData={copyYearList} headlist={upHeadListFlowRateModalYear} pageSize={0} total={0} cellClick={undefined} onPageChange={() => {}} className="h-[370px]" boldArray={[0,1,2]}/> + <UiTable headName="유량현황" tableData={copyYearList} headlist={upHeadListFlowRateModalYear} pageSize={0} total={0} + cellClick={undefined} onPageChange={() => { }} className="h-[370px]" boldArray={[0, 1, 2]} + rowClassName={rainRowClassName} /> </div> <div className="mt-20"> - <UnderComment commentArray={["미처리하수 추정량 산식 =[(강우시 일별유입유량*강우시 일별유입BOD)-(청천시 연평균유입유량*청천시 연평균유입BOD)]/강우시 일별유입BOD)","강우량 3mm 이상만 날과 강우영향일(강우일+2일)의 날"]} /> + <UnderComment commentArray={["미처리하수 추정량 산식 =[(강우시 일별유입유량*강우시 일별유입BOD)-(청천시 연평균유입유량*청천시 연평균유입BOD)]/강우시 일별유입BOD)", "강우량 3mm 이상만 날과 강우영향일(강우일+2일)의 날"]} /> </div> </TabsContent> </UITab> </> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/flowRate/page.tsx b/apps/fo/app/pages/(with-layout)/flowRate/page.tsx index 77d96fff6..9c0bc2386 100644 --- a/apps/fo/app/pages/(with-layout)/flowRate/page.tsx +++ b/apps/fo/app/pages/(with-layout)/flowRate/page.tsx @@ -5,13 +5,13 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; -import type { ExcelType } from "@/app/store/flowRate"; -import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +// import type { ExcelType } from "@/app/store/flowRate"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import FlowRateModal from "./modal/page"; export default function ProcessFacilitySearch() { @@ -21,6 +21,11 @@ export default function ProcessFacilitySearch() { ,initializeCommonData ,searchFlowRateList } = useFoStore((state) => state); + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const searchInputRef = useRef<HTMLDivElement & HTMLInputElement>(null); @@ -38,6 +43,11 @@ export default function ProcessFacilitySearch() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { flowRateSearchLabelArray } = FlowRateSearch; @@ -49,7 +59,6 @@ export default function ProcessFacilitySearch() { upHeadList3, flowRateList, flowRateCount, - excelList, isLoading, } = FlowRateList; @@ -57,20 +66,20 @@ export default function ProcessFacilitySearch() { const [fcltCpct, setFcltCpct] = useState<string>("0"); const [fcltyName, setFcltyName] = useState<string>(""); const [operationValue, setOperationValue] = useState<string>("0"); - const [searchYearValue, setSearchYearValue] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYearValue, setSearchYearValue] = useState<string>(defaultSearchYear); // const [widthSize, setWidthSize] = useState<string>("w-[120%]"); const [upheadList, setUpheadList] = useState<TableUpperProps[]>(upHeadList1); const [resetPage, setResetPage] = useState<boolean>(false); - const prevExcelListRef = useRef<ExcelType[] | null>(null); + // const prevExcelListRef = useRef<ExcelType[] | null>(null); // 모달 상태 추가 const [selectedRow, setSelectedRow] = useState<number | null>(null); const [fcltyNm, setFcltyNm] = useState<string>(""); const [fcltyCd, setFcltyCd] = useState<string>(""); - const [excelTrigger, setExcelTrigger] = useState<boolean>(false); + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); const [initTrigger, setInitTrigger] = useState<boolean>(false); - const searchFlowRate = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchFlowRate = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { let nameCd, partStr; switch (partValue) { @@ -89,9 +98,9 @@ export default function ProcessFacilitySearch() { break; } if(partStr==='G'){ - searchFlowRateList(pageNo, pageCnt, searchYearValue); + await searchFlowRateList(pageNo, pageCnt, searchYearValue); }else{ - searchFlowRateList(pageNo, pageCnt, searchYearValue, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + await searchFlowRateList(pageNo, pageCnt, searchYearValue, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); } }, [searchFlowRateList, searchYearValue, fcltCpct, operationValue, fcltyName]); @@ -99,7 +108,7 @@ export default function ProcessFacilitySearch() { // setWidthSize("w-[120%]"); setUpheadList(upHeadList1) // 상태값 설정 - setSearchYearValue(selectSearchYear[0]?.val || "0"); + setSearchYearValue(defaultSearchYear); setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); // SearchInputSelect 초기화 @@ -146,17 +155,6 @@ export default function ProcessFacilitySearch() { } }, [isInit, initializeCommonData, searchFlowRateList, selectSearchYear, searchFlowRate, initTrigger]); - useEffect(() => { - if(!excelTrigger) return; - - if (excelList && excelList.length > 0) { - downloadExcelFormat({ data: JSON.parse(JSON.stringify(excelList)), fileName: '시설별 유량현황', sheetNameArray: ['시설별 유량현황'], headerRange: 'A1:O1', decimalArray0: [9,10,11,12,13,14] }); - } else if (prevExcelListRef.current && prevExcelListRef.current.length > 0) { - alert('다운받을 데이터가 없습니다.'); - } - prevExcelListRef.current = excelList; - }, [excelList, excelTrigger]); - const ref = useRef<ReturnType>({ partValue: '', sidoValue: '11', @@ -175,15 +173,49 @@ export default function ProcessFacilitySearch() { setResetPage(true); }, [upHeadList1, upHeadList2, upHeadList3]); - const excelDownload = useCallback(() => { - searchFlowRate(ref.current, 1, 10000); - setExcelTrigger(true); - }, [searchFlowRate]); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + if (ref.current.partValue === 'A') { // 행정별 + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'R') { // 유역별 + if (!ref.current.regionValue || ref.current.regionValue === '0') { + alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.unitWaterShedValue || ref.current.unitWaterShedValue === '0') { + alert('엑셀 다운로드는 단위유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'G') { // 환경청별 + if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } + setIsExcelDownClick(true); + try { + await searchFlowRate(ref.current, 1, 10000); + const latestExcelList = store.getState().FlowRateList.excelList || []; + if (latestExcelList.length > 0) { + downloadExcelFormat({ data: JSON.parse(JSON.stringify(latestExcelList)), fileName: '시설별 유량현황', sheetNameArray: ['시설별 유량현황'], headerRange: 'A1:O1', decimalArray0: [9,10,11,12,13,14] }); + } else { + alert('조회된 결과가 없습니다.'); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFlowRate, store]); const total = flowRateCount; return ( <MainDiv> - <Title title="시설별 유량현황" /> <ContentsDiv> <SearchBaseForm leftChildren={<> @@ -203,6 +235,7 @@ export default function ProcessFacilitySearch() { queryClick={chkVal} excelClick={excelDownload} initClick={resetVal} + excelDisabled={isExcelDownClick} ref={ref} isAdmin={false} initSidoValue={'11'}/> <ListDiv> <LoadingOverlay isLoading={isLoading} /> @@ -216,7 +249,7 @@ export default function ProcessFacilitySearch() { {selectedRow !== null && ( <UiModal body={<FlowRateModal fcltyName={fcltyNm} facilityCd={fcltyCd} searchYear={searchYearValue}></FlowRateModal>} - bodyClass="sm:max-w-[90vw] w-[1250px] h-[780px]" + bodyClass="sm:max-w-[90vw] w-[1250px] h-[100dvh] max-h-[780px]" open={selectedRow !== null} onOpenChange={(open: boolean) => !open && closeModal()} /> diff --git a/apps/fo/app/pages/(with-layout)/layout.tsx b/apps/fo/app/pages/(with-layout)/layout.tsx index 851d79497..e1f6f664e 100644 --- a/apps/fo/app/pages/(with-layout)/layout.tsx +++ b/apps/fo/app/pages/(with-layout)/layout.tsx @@ -4,6 +4,8 @@ import { BaseLayout } from "@common/components"; import type { MenuItem } from "@/app/store/common"; import { useFoStore } from "@/app/store"; import { usePathname, useRouter } from "next/navigation"; +import { useMenuStore } from "@/src/shared/lib"; +import { PageLayout } from "@/src/shared/ui/page-layout"; export default function RootLayout({ children, @@ -11,10 +13,12 @@ export default function RootLayout({ children: React.ReactNode; }>) { const { getMenuAuth } = useFoStore((state) => state); + const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); const pathname = usePathname(); const router = useRouter(); const handleClick = (id: MenuItem) => { + setCurrentMenuInfo(id); getMenuAuth(id.menuCd); router.push(id.urlPath); }; @@ -24,7 +28,7 @@ export default function RootLayout({ currentPath={pathname ?? ""} handleMenuClick={handleClick} > - {children} + <PageLayout>{children}</PageLayout> </BaseLayout> ); } diff --git a/apps/fo/app/pages/(with-layout)/planSewage/page.tsx b/apps/fo/app/pages/(with-layout)/planSewage/page.tsx index 1f8bd1e94..cd3c9b053 100644 --- a/apps/fo/app/pages/(with-layout)/planSewage/page.tsx +++ b/apps/fo/app/pages/(with-layout)/planSewage/page.tsx @@ -5,11 +5,11 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; -import { downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiTable, type ReturnType } from "@common/business_components"; +import { downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; import UnderComment from "@/app/components/common/UnderComment"; @@ -20,13 +20,18 @@ const commentArray = [ ] export default function PlanSewage() { - const {Common - ,PlanSewage - ,PlanSewageSearch - ,initializeCommonData - ,searchPlanSewageList - } = useFoStore((state) => state) || { processAreaList: { processAreaList: [] }}; - + const { Common + , PlanSewage + , PlanSewageSearch + , initializeCommonData + , searchPlanSewageList + } = useFoStore((state) => state) || { processAreaList: { processAreaList: [] } }; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } + const { isInit, selectUpdownData, @@ -41,8 +46,13 @@ export default function PlanSewage() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { - labelArray, + labelArray, } = PlanSewageSearch; const { @@ -51,7 +61,6 @@ export default function PlanSewage() { upHeadList3, planSewageList, planSewageCount, - excelList, isLoading, } = PlanSewage; @@ -69,9 +78,9 @@ export default function PlanSewage() { // 그리드 관련 const searchInputRef = useRef<HTMLDivElement & HTMLInputElement>(null); - + // 모달 상태 추가 - + // 초기화 변수 const [resetPage, setResetPage] = useState<boolean>(false); // 표 너비 @@ -85,11 +94,12 @@ export default function PlanSewage() { // 시설상태 const [operationValue, setOperationValue] = useState<string>("0"); // 조회기간 - const [searchYear, setSearchYear] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState<string>(defaultSearchYear); const [initTrigger, setInitTrigger] = useState<boolean>(false); + // 엑셀다운로드 트리거 + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); - const [excelTrigger, setExcelTrigger] = useState<boolean>(false); /** * 페이지 함수 */ @@ -100,7 +110,7 @@ export default function PlanSewage() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYear(selectSearchYear[0]?.val || "0"); + setSearchYear(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -109,52 +119,39 @@ export default function PlanSewage() { input.value = ""; } } - ref.current.sidoValue='11'; + ref.current.sidoValue = '11'; searchPlanSewage(ref.current, 1, 10); } - useEffect(() => { - if(!excelTrigger) return; - - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: '계획하수량 산정지표 현황', - sheetNameArray: ['계획하수량 산정지표 현황'], - headerRange: 'A1:M1' - }); - } - }, [excelList, excelTrigger]); - /** * 콜백함수 */ // 조회 - const searchPlanSewage = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchPlanSewage = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt: number = 20) => { let nameCd, partStr switch (partValue) { case 'R': if (regionValue !== '0') { - if(unitWaterShedValue==='0') nameCd = regionValue; - else if (middleRegionValue==='0') nameCd = unitWaterShedValue; + if (unitWaterShedValue === '0') nameCd = regionValue; + else if (middleRegionValue === '0') nameCd = unitWaterShedValue; else nameCd = middleRegionValue; } partStr = partValue break; default: - if(sidoValue !== '0') nameCd = ref.current.sidoValue!="0"?ref.current.sidoValue:sidoValue; - if(sigunValue !== '0') nameCd = sigunValue; - partStr = envPartValue !== '0' ? envPartValue:partValue; + if (sidoValue !== '0') nameCd = ref.current.sidoValue != "0" ? ref.current.sidoValue : sidoValue; + if (sigunValue !== '0') nameCd = sigunValue; + partStr = envPartValue !== '0' ? envPartValue : partValue; break; } - if(partStr === 'G'){ - partValue = partValue==='G'?'A':partValue; - searchPlanSewageList(pageNo, pageCnt, searchYear, partValue); + if (partStr === 'G') { + partValue = partValue === 'G' ? 'A' : partValue; + await searchPlanSewageList(pageNo, pageCnt, searchYear, partValue); + } + else { + await searchPlanSewageList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); } - else{ - searchPlanSewageList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); - } }, [operationValue, fcltyName, searchPlanSewageList, searchYear, fcltCpct]); /** @@ -162,25 +159,83 @@ export default function PlanSewage() { */ useEffect(() => { if (!isInit) { - if(!initTrigger){ + if (!initTrigger) { initializeCommonData(); searchPlanSewage(ref.current, 1, 10); setInitTrigger(true); } } - }, [isInit, initializeCommonData, searchPlanSewageList, selectSearchYear, initTrigger, searchPlanSewage]); + }, [isInit, initializeCommonData, initTrigger, searchPlanSewage]); - const chkVal = useCallback((returntype:ReturnType) => { + const chkVal = useCallback((returntype: ReturnType) => { // setWidthSize(returntype.partValue=='A' ?"w-[120%]":"w-[130%]") - setUpheadList(returntype.partValue == 'R' ? upHeadList2 : returntype.partValue == 'G'?upHeadList3:upHeadList1); + setUpheadList(returntype.partValue == 'R' ? upHeadList2 : returntype.partValue == 'G' ? upHeadList3 : upHeadList1); setResetPage(true); }, [upHeadList1, upHeadList2, upHeadList3]); // 엑셀다운로드 - const excelDownload = useCallback((searchReturn: ReturnType) => { - searchPlanSewage(searchReturn, 1, 10000); - setExcelTrigger(true); - }, [searchPlanSewage]); + const excelDownload = useCallback(async (searchReturn: ReturnType) => { + if (isExcelDownClick) return; + if (ref.current.partValue === 'A') { // 행정별 + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'R') { // 유역별 + if (!ref.current.regionValue || ref.current.regionValue === '0') { + alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'G') { // 환경청별 + if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + return; + } + } + setIsExcelDownClick(true); + try { + await searchPlanSewage(searchReturn, 1, 10000); + const latestExcelList = store.getState().PlanSewage.excelList || []; + if (latestExcelList.length > 0) { + const toNumber = (value: unknown) => { + if (value === null || value === undefined || value === '') return ''; + const parsed = typeof value === "number" ? value : Number(String(value).replace(/,/g, "")); + return Number.isFinite(parsed) ? parsed : ''; + }; + + const numericKeys = [ + "시설용량(m³/일)", + "여유용량(m³/일)", + "변동부하율(%)", + "분류식화율(%)", + ]; + + const excelData = latestExcelList.map((row) => { + const nextRow = { ...row } as Record<string, unknown>; + numericKeys.forEach((key) => { + if (key in nextRow) { + nextRow[key] = toNumber(nextRow[key]); + } + }); + return nextRow; + }); + + downloadExcelFormat({ + data: excelData as unknown as JSON[], + fileName: '계획하수량 산정지표 현황', + sheetNameArray: ['계획하수량 산정지표 현황'], + headerRange: 'A1:M1', + decimalArray0: [9, 10], + decimalArray1: [12], + decimalArray2: [11] + }); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchPlanSewage, store]); // 페이지 변경 const handlePageChange = useCallback((page: number) => { @@ -190,11 +245,10 @@ export default function PlanSewage() { const mainList = planSewageList || []; const total = planSewageCount || 0; - + return ( <MainDiv> - <Title title="계획하수량 산정지표 현황" /> - <SearchBaseForm + <SearchBaseForm leftChildren={<> <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> <SearchInput className={"col-span-3"} label={labelArray[4] as string} textValue={fcltyName} setText={setFcltyName} /> @@ -211,17 +265,18 @@ export default function PlanSewage() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} - ref={ref} isAdmin={false} initSidoValue={"11"}/> + ref={ref} isAdmin={false} initSidoValue={"11"} /> <ListDiv> <LoadingOverlay isLoading={isLoading} /> <div className="h-full w-full overflow-x-scroll min-[1200px]:overflow-x-hidden"> - <div className="w-[120%] min-[1200px]:w-[100%]"> - <UiTable cellClick={ undefined } headName={"계획하수량 산정지표 현황"} tableData={mainList} headlist={upheadList} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> + <div className="w-[120%] min-[1200px]:w-[100%]"> + <UiTable cellClick={undefined} headName={"계획하수량 산정지표 현황"} tableData={mainList} headlist={upheadList} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> </div> <UnderComment divHeight={"5"} commentArray={commentArray} /> </div> </ListDiv> </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/processArea/page.tsx b/apps/fo/app/pages/(with-layout)/processArea/page.tsx index 91bdb6ab0..5e4ad0b5b 100644 --- a/apps/fo/app/pages/(with-layout)/processArea/page.tsx +++ b/apps/fo/app/pages/(with-layout)/processArea/page.tsx @@ -5,27 +5,36 @@ */ "use client" -import { useFoStore } from "@/app/store"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { StoreContext, useFoStore } from "@/app/store"; +import { useShallow } from 'zustand/react/shallow'; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import ProcessFacilityInfoModal from "./modal/page"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; export default function ProcessArea() { - const {Common, + const { + Common, ProcessArea, ProcessAreaSearch, initializeCommonData, searchProcessAreaList - } = useFoStore((state) => state) || { - Common: { isInit: false, selectSidoData: [], selectSigunData: [], selectRegionData: [], selectUnitWaterShedData: [], selectMiddleRegionData: [], selectEnvPartData: [], selectSearchYear: [{ val: "0" }], btnClass: "" }, - ProcessArea: { processAreaList: [], processAreaCount: 0, excelList: [], upHeadList1: [] }, - ProcessAreaSearch: { processFacilityLabelArray: [] }, - initializeCommonData: () => {}, - searchProcessAreaList: () => {} - }; - + } = useFoStore( + useShallow((state) => ({ // 다른 무관한 상태가 변경되어도 이 컴포넌트는 불필요하게 다시 렌더링 되지 않음. + Common: state.Common, + ProcessArea: state.ProcessArea, + ProcessAreaSearch: state.ProcessAreaSearch, + initializeCommonData: state.initializeCommonData, + searchProcessAreaList: state.searchProcessAreaList, + })) + ); + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } + const { isInit, selectSidoData, @@ -37,7 +46,12 @@ export default function ProcessArea() { selectSearchYear, btnClass, } = Common; - + + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { processFacilityLabelArray, } = ProcessAreaSearch; @@ -46,7 +60,6 @@ export default function ProcessArea() { upHeadList1, processAreaList, processAreaCount, - excelList, isLoading, } = ProcessArea; @@ -54,13 +67,15 @@ export default function ProcessArea() { // 그리드 관련 const searchInputRef = useRef<HTMLDivElement & HTMLInputElement>(null); - // 모달 상태 추가 + // 모달 상태 추가 const [selectedRow, setSelectedRow] = useState<number | null>(null); const [fcltyNm, setFcltyNm] = useState<string>(""); const [fcltyCd, setFcltyCd] = useState<string>(""); - + const [resetPage, setResetPage] = useState<boolean>(false); - const [searchYearValue, setSearchYearValue] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYearValue, setSearchYearValue] = useState<string>(defaultSearchYear); + + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); const ref = useRef<ReturnType>({ partValue: '', @@ -74,8 +89,8 @@ export default function ProcessArea() { sigunText: '', }); - const searchProcessArea = useCallback(({ sidoText, sigunText}: ReturnType, pageNo: number, pageCnt:number=20) => { - searchProcessAreaList(pageNo, pageCnt, sidoText, sigunText, searchYearValue); + const searchProcessArea = useCallback(async ({ sidoText, sigunText }: ReturnType, pageNo: number, pageCnt: number = 20) => { + await searchProcessAreaList(pageNo, pageCnt, sidoText, sigunText, searchYearValue); }, [searchProcessAreaList, searchYearValue]); // 조회 @@ -91,8 +106,8 @@ export default function ProcessArea() { input.value = ""; } } - setSearchYearValue(selectSearchYear[0]?.val || "0"); - searchProcessAreaList(1, 10, '', '', selectSearchYear[0]?.val || "0"); + setSearchYearValue(defaultSearchYear); + searchProcessAreaList(1, 10, '', '', defaultSearchYear); } // 셀 클릭 시 모달 표시 @@ -112,24 +127,49 @@ export default function ProcessArea() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchProcessAreaList(1, 10, '', '', selectSearchYear[0]?.val || "0"); + searchProcessAreaList(1, 10, '', '', defaultSearchYear); } - }, [isInit, initializeCommonData, searchProcessAreaList]); + }, [isInit, initializeCommonData, searchProcessAreaList, defaultSearchYear]); - useEffect(() => { - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: '처리구역정보', - sheetNameArray: ['처리구역정보'], - headerRange: 'A1:W1' - }); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + // if (ref.current.partValue === 'A') { // 행정별 + // if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + // alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'R') { // 유역별 + // if (!ref.current.regionValue || ref.current.regionValue === '0') { + // alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'G') { // 환경청별 + // if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + // alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } + + setIsExcelDownClick(true); + try { + await searchProcessArea(ref.current, 1, 10000); + const latestExcelList = store.getState().ProcessArea.excelList || []; + if (latestExcelList.length > 0) { + downloadExcelFormat({ + data: latestExcelList as unknown as JSON[], + fileName: '처리구역정보', + sheetNameArray: ['처리구역정보'], + headerRange: 'A1:W1', + decimalArray0: [3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15], + decimalArray2: [16, 17, 18, 19, 20, 21], + }); + } else { + alert('조회된 결과가 없습니다.'); + } + } finally { + setIsExcelDownClick(false); } - }, [excelList]); - - const excelDownload = useCallback(() => { - searchProcessArea(ref.current, 1, 10000); - }, [searchProcessArea]); + }, [isExcelDownClick, searchProcessArea, store]); const handlePageChange = useCallback((pageNo: number) => { setResetPage(false); @@ -139,11 +179,10 @@ export default function ProcessArea() { const total = processAreaCount; return ( <MainDiv> - <Title title="처리구역정보" /> <ContentsDiv> <SearchBaseForm leftChildren={<> - <SelectBox className="col-span-3" label={processFacilityLabelArray[6] as string} selectArray={selectSearchYear} onSelectValue={(val) => setSearchYearValue(val.toString())} value={searchYearValue} selectClass={undefined}/> + <SelectBox className="col-span-3" label={processFacilityLabelArray[6] as string} selectArray={selectSearchYear} onSelectValue={(val) => setSearchYearValue(val.toString())} value={searchYearValue} selectClass={undefined} /> </>} rightChildren={undefined} selectSidoData={selectSidoData} @@ -155,6 +194,7 @@ export default function ProcessArea() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={true} @@ -163,19 +203,20 @@ export default function ProcessArea() { <LoadingOverlay isLoading={isLoading} /> <div className="h-full w-full overflow-x-scroll min-[1900px]:overflow-x-hidden"> <div className="w-[160%] min-[1900px]:w-[100%]"> - <UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} headName={"처리구역정보"} tableData={mainList} headlist={upHeadList1} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> + <UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} headName={"처리구역정보"} tableData={mainList} + headlist={upHeadList1} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> </div> </div> </ListDiv> </ContentsDiv> {selectedRow !== null && ( - <UiModal - body={<ProcessFacilityInfoModal fcltyName={fcltyNm} facilityCd={fcltyCd}></ProcessFacilityInfoModal>} - bodyClass="sm:max-w-[90vw] w-[1200px] h-[900px]" + <UiModal + body={<ProcessFacilityInfoModal fcltyName={fcltyNm} facilityCd={fcltyCd}></ProcessFacilityInfoModal>} + bodyClass="sm:max-w-[90vw] w-[1200px] h-[100dvh] max-h-[900px] overflow-y-auto" open={selectedRow !== null} onOpenChange={(open: boolean) => !open && closeModal()} /> )} </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx b/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx index 7e3f7c52f..3118e008f 100644 --- a/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx @@ -5,6 +5,7 @@ */ "use client" +import { useCallback, useRef } from "react"; import { useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/dashboard"; import { downloadExcelFormat, filePickedBlobArray, UITab, UiTable } from "@common/business_components/ui"; @@ -121,6 +122,21 @@ export default function ProcessFacilityInfoModal(props: ModalType) { const upHeadListPlanInfoChange = headMakeList(upHeadListPlanInfo, 2); const upHeadListFcltyHistInfoChange = headMakeList(upHeadListFcltyHistInfo, 3); const upHeadListFcltyWaterQltyInfoChange = headMakeList(upHeadListFcltyWaterQltyInfo, 4); + const historyTopScrollRef = useRef<HTMLDivElement>(null); + const historyBottomScrollRef = useRef<HTMLDivElement>(null); + const syncLockRef = useRef(false); + + const syncScroll = useCallback((source: HTMLDivElement | null, target: HTMLDivElement | null) => { + if (!source || !target || syncLockRef.current) return; + syncLockRef.current = true; + const nextTop = source.scrollTop; + if (target.scrollTop !== nextTop) { + target.scrollTop = nextTop; + } + requestAnimationFrame(() => { + syncLockRef.current = false; + }); + }, []); const excelDownloadPlan = () => { const excelDownList: any[] = []; @@ -137,11 +153,19 @@ export default function ProcessFacilityInfoModal(props: ModalType) { "시운전완료일": item.trlrnCmplDt, "가동개시일": item.rnDt, "시설용량(m³/일)": item.fctCpc&&item.fctCpc.toString().trim()!=''?Number(item.fctCpc.toString().replaceAll(',','')):'', + "공법구분": item.trtSrsHanNm, + "공법계열": item.trtMocHanNm, + "방법": item.trtFrfHanNm, }); }); } if (excelDownList && excelDownList.length > 0) { - downloadExcelFormat({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.fcltyName + ' 시설이력관리', sheetNameArray: [props.fcltyName + ' 시설이력관리'], headerRange: 'A1:I1' }); + downloadExcelFormat({ + data: JSON.parse(JSON.stringify(excelDownList)), + fileName: props.fcltyName + ' 시설이력관리', + sheetNameArray: [props.fcltyName + ' 시설이력관리'], + decimalArray0: [8], + headerRange: 'A1:L1' }); } else if (gridListFcltyHistInfo.length == 0) { alert('다운받을 데이터가 없습니다.'); } @@ -182,7 +206,7 @@ export default function ProcessFacilityInfoModal(props: ModalType) { const sheetParameter = [ { data: excelData, - headerNum: 2, + headerNum: 3, headerRange: 'A1:S2', sheetName: '시설 수질정보', startCol: 'A' @@ -218,7 +242,7 @@ export default function ProcessFacilityInfoModal(props: ModalType) { <h3 className="text-2xl font-medium text-label">시설 기본정보</h3> </div> <div className="card !m-[1.2rem] max-h-[40rem] overflow-auto !p-0r"> - <Table> + <Table variant="secondary"> <thead> <TableRow className=""> <TableHead className="w-1/6">{'시설명'}</TableHead> @@ -262,7 +286,11 @@ export default function ProcessFacilityInfoModal(props: ModalType) { <TabsContent value="history_info"> <ListDiv> - <div className="h-[250px] w-full overflow-y-auto overflow-x-hidden"> + <div + className="h-[250px] w-full overflow-y-auto overflow-x-hidden" + ref={historyTopScrollRef} + onScroll={() => syncScroll(historyTopScrollRef.current, historyBottomScrollRef.current)} + > <Button className={btnClass} style={{float: 'right'}} color="green" onClick={excelDownloadPlan}> {"엑셀다운로드"} </Button> @@ -274,7 +302,11 @@ export default function ProcessFacilityInfoModal(props: ModalType) { </div> </ListDiv> <ListDiv> - <div className="h-[250px] w-full overflow-y-auto overflow-x-hidden"> + <div + className="h-[250px] w-full overflow-y-auto overflow-x-hidden" + ref={historyBottomScrollRef} + onScroll={() => syncScroll(historyBottomScrollRef.current, historyTopScrollRef.current)} + > <Button className={btnClass} style={{float: 'right'}} color="green" onClick={excelDownloadQlty}> {"엑셀다운로드"} </Button> @@ -292,4 +324,4 @@ export default function ProcessFacilityInfoModal(props: ModalType) { </div> </> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/processFacility/page.tsx b/apps/fo/app/pages/(with-layout)/processFacility/page.tsx index 856ab4631..302a4e8b7 100644 --- a/apps/fo/app/pages/(with-layout)/processFacility/page.tsx +++ b/apps/fo/app/pages/(with-layout)/processFacility/page.tsx @@ -6,11 +6,12 @@ "use client" import UnderComment from "@/app/components/common/UnderComment"; -import { useFoStore } from "@/app/store"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { StoreContext, useFoStore } from "@/app/store"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox + , UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import ProcessFacilityInfoModal from "./modal/page"; export default function ProcessFacilitySearch() { @@ -25,6 +26,11 @@ export default function ProcessFacilitySearch() { //시설데이터 수질정보 조회 ,searchProcessFacilityWaterQlty } = useFoStore((state) => state) || { ProcessFacilityList: { processFacilityList: [], processFacilityCount: 0 }}; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { isInit, @@ -69,7 +75,6 @@ export default function ProcessFacilitySearch() { upHeadList1, upHeadList2, upHeadList3, - excelList, isLoading, } = ProcessFacilityList; @@ -89,6 +94,7 @@ export default function ProcessFacilitySearch() { const [partValue, setPartValue] = useState<string>("A"); // const [widthSize, setWidthSize] = useState<string>("w-[120%]"); const [upHeadList, setUpheadList] = useState(upHeadList1); + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); /** * 모달 상태 변수 @@ -149,13 +155,7 @@ export default function ProcessFacilitySearch() { } }, [partValue, upHeadList1, upHeadList2, upHeadList3]); - useEffect(() => { - if (excelList && excelList.length > 0) { - downloadExcel({ data: excelList as unknown as JSON[], fileName: '처리시설 시설정보', sheetNameArray: ['처리시설 시설정보'], headerRange: 'A1:P1' }); - } - }, [excelList]); - - const searchFcltyList = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=10) => { + const searchFcltyList = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=10) => { let nameCd, partStr switch (partValue) { case 'R': @@ -172,7 +172,7 @@ export default function ProcessFacilitySearch() { partStr = envPartValue !== '0' ? envPartValue:partValue; break; } - searchProcessFacility(pageNo, pageCnt, partStr, nameCd || '0', fcltCpct, operationValue, facilityPartValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + await searchProcessFacility(pageNo, pageCnt, partStr, nameCd || '0', fcltCpct, operationValue, facilityPartValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); if(isSearch) { setIsSearch(false); } @@ -235,9 +235,44 @@ export default function ProcessFacilitySearch() { } } - const excelDownload = useCallback(() => { - searchFcltyList(ref.current, 1, 10000); - }, [searchFcltyList]); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + // if (ref.current.partValue === 'A') { // 행정별 + // if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + // alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'R') { // 유역별 + // if (!ref.current.regionValue || ref.current.regionValue === '0') { + // alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'G') { // 환경청별 + // if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + // alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } + + setIsExcelDownClick(true); + try { + await searchFcltyList(ref.current, 1, 10000); + const latestExcelList = store.getState().ProcessFacilityList.excelList || []; + if (latestExcelList.length > 0) { + downloadExcelFormat({ + data: latestExcelList as unknown as JSON[], + fileName: '처리시설 시설정보', + sheetNameArray: ['처리시설 시설정보'], + headerRange: 'A1:P1', + decimalArray0: [9, 10, 11], + }); + } else { + alert('조회된 결과가 없습니다.'); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFcltyList, store]); // 모달 닫기 const closeModal = () => { @@ -248,7 +283,6 @@ export default function ProcessFacilitySearch() { return ( <MainDiv> - <Title title="처리시설 시설정보" /> <ContentsDiv> <SearchBaseForm leftChildren={<> @@ -266,6 +300,7 @@ export default function ProcessFacilitySearch() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={false} /> <ListDiv> @@ -292,11 +327,11 @@ export default function ProcessFacilitySearch() { {selectedRow !== null && ( <UiModal body={<ProcessFacilityInfoModal fcltyName={modalFcltyNm} facilityCd={modalFcltyCd} sido={modalSido} gugun={modalGugun} daddr={modalAddr} fctCpc={modalFctCpc} trtFrfNm={modalTrtFrfNm} rnDt={modalRnDt} cmplDt={modalCmplDt} trstOprEtpNm={modalTrstOprEtpNm} bscFctClsNm={modalBscFctClsNm} mnstr={modalMnstr} efflWtrrgn={modalEfflWtrrgn} lgbsn={modalLgbsn} efflWqltStndrdNm={modalEfflWqltStndrdNm} rgnNm={modalRgnNm} rvbsn={modalRvbsn}></ProcessFacilityInfoModal>} - bodyClass="sm:max-w-[70vw] sm:max-h-[95vh]" + bodyClass="sm:max-w-[100vw] w-[1300px] h-[100dvh] max-h-[800px]" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} /> )} </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/rdi/page.tsx b/apps/fo/app/pages/(with-layout)/rdi/page.tsx index 47142e0a1..8a2c87956 100644 --- a/apps/fo/app/pages/(with-layout)/rdi/page.tsx +++ b/apps/fo/app/pages/(with-layout)/rdi/page.tsx @@ -5,11 +5,11 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiTable, type ReturnType } from "@common/business_components"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; import UnderComment from "@/app/components/common/UnderComment"; @@ -26,6 +26,11 @@ export default function Rdi() { ,RdiSearch ,searchRdiList } = useFoStore((state) => state) || { processAreaList: { processAreaList: [] }}; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { isInit, @@ -41,6 +46,11 @@ export default function Rdi() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { labelArray, } = RdiSearch; @@ -51,7 +61,6 @@ export default function Rdi() { upHeadList3, rdiList, rdiCount, - excelList, isLoading, } = Rdi; @@ -83,9 +92,10 @@ export default function Rdi() { // 시설상태 const [operationValue, setOperationValue] = useState<string>("0"); // 조회기간 - const [searchYear, setSearchYear] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState<string>(defaultSearchYear); // 엑셀 다운로드 트리거 - const [isInitExcelDownload, setIsInitExcelDownload] = useState<boolean>(false); + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); + const [initTrigger, setInitTrigger] = useState<boolean>(false); /** @@ -99,7 +109,7 @@ export default function Rdi() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYear(selectSearchYear[0]?.val || "0"); + setSearchYear(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -112,23 +122,11 @@ export default function Rdi() { searchRdi(ref.current, 1, 10); } - useEffect(() => { - if(!isInitExcelDownload) return ; - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: 'RDII추정', - sheetNameArray: ['RDII추정'], - headerRange: 'A1:R1' - }); - } - }, [excelList, isInitExcelDownload]); - /** * 콜백함수 */ // 조회 - const searchRdi = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchRdi = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { let nameCd, partStr switch (partValue) { case 'R': @@ -147,10 +145,9 @@ export default function Rdi() { } if(partStr === 'G'){ - searchRdiList(pageNo, pageCnt, searchYear); + await searchRdiList(pageNo, pageCnt, searchYear); }else{ - searchRdiList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); - + await searchRdiList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); } }, [operationValue, fcltyName, searchRdiList, searchYear, fcltCpct]); @@ -174,10 +171,71 @@ export default function Rdi() { }, [upHeadList1, upHeadList2, upHeadList3]); // 엑셀다운로드 - const excelDownload = useCallback(() => { - searchRdi(ref.current, 1, 5000); - setIsInitExcelDownload(true); - }, [searchRdi]); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + // if (ref.current.partValue === 'A') { // 행정별 + // if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + // alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'R') { // 유역별 + // if (!ref.current.regionValue || ref.current.regionValue === '0') { + // alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } else if (ref.current.partValue === 'G') { // 환경청별 + // if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + // alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + // return; + // } + // } + + setIsExcelDownClick(true); + try { + await searchRdi(ref.current, 1, 5000); + const latestExcelList = store.getState().Rdi.excelList || []; + if (latestExcelList.length > 0) { + const toNumber = (value: unknown) => { + if (value === null || value === undefined || value === '') return ''; + const parsed = typeof value === "number" ? value : Number(String(value).replace(/,/g, "")); + return Number.isFinite(parsed) ? parsed : ''; + }; + + const numericKeys = [ + "시설용량(m³/일)", + "청천시유입하수량(m³/일)", + "하절기강우일최저유입BOD(mg/L)", + "하절기최저유입BOD유입유량(m³/일)", + "동절기청천시유입BOD(mg/L)", + "RDII추정", + "RDII추정시일강수량(mm)", + "강우시총추정유입량(m³/일)", + ]; + + const excelData = latestExcelList.map((row) => { + const nextRow = { ...row } as Record<string, unknown>; + numericKeys.forEach((key) => { + if (key in nextRow) { + nextRow[key] = toNumber(nextRow[key]); + } + }); + return nextRow; + }); + + downloadExcelFormat({ + data: excelData as unknown as JSON[], + fileName: 'RDII추정', + sheetNameArray: ['RDII추정'], + headerRange: 'A1:R1', + decimalArray0: [9, 10, 11, 12, 13, 14, 15, 16], + }); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchRdi, store]); // 페이지 변경 const handlePageChange = useCallback((page: number) => { @@ -190,8 +248,7 @@ export default function Rdi() { return ( <MainDiv> - <Title title="RDII추정" /> - <ContentsDiv> + <ContentsDiv> <SearchBaseForm leftChildren={<> <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> @@ -209,13 +266,15 @@ export default function Rdi() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={false} initSidoValue={"11"}/> <ListDiv> <LoadingOverlay isLoading={isLoading} /> <div className="h-full w-full overflow-x-scroll min-[1200px]:overflow-x-hidden"> <div className="w-[120%] min-[1200px]:w-[100%]"> - <UiTable cellClick={ undefined } headName={"RDII추정"} tableData={mainList} headlist={upheadList} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> + <UiTable cellClick={ undefined } headName={"RDII추정"} tableData={mainList} headlist={upheadList} + pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange} widthFixed={true} /> </div> <UnderComment divHeight={"0"} commentArray={commentArray} /> </div> @@ -223,4 +282,4 @@ export default function Rdi() { </ContentsDiv> </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx index bc7fec4e8..223dc055a 100644 --- a/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/sewageOccur/modal/page.tsx @@ -8,7 +8,7 @@ import { roundNumber } from "@/app/components/common/DataForm"; import { useFoStore } from "@/app/store"; import type { modalTableType } from "@/app/store/sewageOccur"; -import { DateMonthPicker, downloadExcel, UiTable } from "@common/business_components/ui"; +import { DateMonthPicker, downloadExcelHeaders, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Table, TableCell, TableHead, TableRow } from "@common/components/ui"; import { useEffect, useState } from "react"; @@ -69,20 +69,25 @@ export default function SewageOccurModal(props: { fcltyName: string; facilityCd } }, [endYearMonth, startYearMonth]); - const excelDownload = () => { + const excelDownload = () => { + // 헤더 순서를 명시적으로 정의 + const headers = ["날짜", "물사용량(m³/일)", "오수전환율(%)", "오수발생량 추정(m³/일)"] as const; + const excelDownList: any[] = []; if(detailList.length > 0){ detailList.forEach((item) => { - excelDownList.push({ - "날짜": item.msrDt, - "물사용량(m³/일)": roundNumber(item.waterUsage, 0), - "오수전환율(%)": roundNumber(item.swageChangePercent, 0), - "오수발생량 추정(m³/일)": roundNumber(item.estimationSewage, 0), - }); + // 순서를 보장하기 위해 헤더 순서대로 객체 생성 + const row: Record<string, string | number> = {}; + row[headers[0]] = item.msrDt; + row[headers[1]] = roundNumber(item.waterUsage, 0); + row[headers[2]] = roundNumber(item.swageChangePercent, 0); + row[headers[3]] = roundNumber(item.estimationSewage, 0); + excelDownList.push(row); }); } if (excelDownList && excelDownList.length > 0) { - downloadExcel({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.searchYear+'년 '+props.fcltyName + ' 오수발생량 추정', sheetNameArray: [props.fcltyName + ' 오수발생량 추정'], headerRange: 'A1:D1' }); + downloadExcelHeaders({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: props.searchYear+'년 '+props.fcltyName + ' 오수발생량 추정' + , sheetNameArray: [props.fcltyName + ' 오수발생량 추정'], headerRange: 'A1:D1', headers }); } else if (detailList.length == 0) { alert('다운받을 데이터가 없습니다.'); } diff --git a/apps/fo/app/pages/(with-layout)/sewageOccur/page.tsx b/apps/fo/app/pages/(with-layout)/sewageOccur/page.tsx index 001e816ae..7a4faf9cd 100644 --- a/apps/fo/app/pages/(with-layout)/sewageOccur/page.tsx +++ b/apps/fo/app/pages/(with-layout)/sewageOccur/page.tsx @@ -6,13 +6,13 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import { type TableUpperProps } from "@/app/store/common"; import type { SewageOccurProps } from "@/app/store/sewageOccur"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import SewageOccurModal from "./modal/page"; export default function PlanSewage() { @@ -29,6 +29,11 @@ export default function PlanSewage() { searchSewageOccurList: () => {}, calculateModalDataForAllFacilities: async () => [] }; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { isInit, @@ -44,6 +49,11 @@ export default function PlanSewage() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { labelArray, } = SewageOccurSearch; @@ -54,7 +64,6 @@ export default function PlanSewage() { upHeadList3, sewageOccurList, sewageOccurCount, - excelList, isLoading, } = SewageOccur; @@ -93,10 +102,9 @@ export default function PlanSewage() { // 시설상태 const [operationValue, setOperationValue] = useState<string>("0"); // 조회기간 - const [searchYear, setSearchYear] = useState<string>(selectSearchYear[0]?.val || "0"); - // 엑셀다운로드 트리거 - const [isInitExcelDownload, setIsInitExcelDownload] = useState<boolean>(false); - + const [searchYear, setSearchYear] = useState<string>(defaultSearchYear); + // 엑셀 조회 중 상태 + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); /** * 페이지 함수 */ @@ -107,7 +115,7 @@ export default function PlanSewage() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYear(selectSearchYear[0]?.val || "0"); + setSearchYear(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -116,7 +124,7 @@ export default function PlanSewage() { input.value = ""; } } - searchSewageOccurList(1, 10, selectSearchYear[0]?.val || "0"); + searchSewageOccurList(1, 10, defaultSearchYear); } // 모달 닫기 @@ -139,32 +147,15 @@ export default function PlanSewage() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchSewageOccurList(1, 10, selectSearchYear[0]?.val || "0"); + searchSewageOccurList(1, 10, defaultSearchYear); } - }, [isInit, initializeCommonData, searchSewageOccurList, selectSearchYear]); - - useEffect(() => { - if(!isInitExcelDownload) return ; - - const timer = setTimeout(() => { - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: '오수발생량 추정', - sheetNameArray: ['오수발생량 추정'], - headerRange: 'A1:O1' - }); - } - }, 500); // 약간의 지연을 두고 다운로드 실행 - - return () => clearTimeout(timer); - }, [excelList, isInit, isInitExcelDownload]); + }, [isInit, initializeCommonData, searchSewageOccurList, defaultSearchYear]); /** * 콜백함수 */ // 조회 - const searchFacilityPrecipitation = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchFacilityPrecipitation = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { let nameCd, partStr switch (partValue) { case 'R': @@ -182,9 +173,10 @@ export default function PlanSewage() { break; } if (partStr.length > 1 || fcltCpct !== '0' || operationValue!== '0' ||nameCd ||(fcltyName.trim() !== "" && fcltyName.trim() !== null)) { - searchSewageOccurList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + await searchSewageOccurList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue + , fcltyName.trim() !== ""? fcltyName : undefined); } else { - searchSewageOccurList(pageNo, pageCnt, searchYear); + await searchSewageOccurList(pageNo, pageCnt, searchYear); } }, [fcltCpct, operationValue, fcltyName, searchSewageOccurList, searchYear]); @@ -195,10 +187,76 @@ export default function PlanSewage() { }, [upHeadList1, upHeadList2, upHeadList3]); // 엑셀다운로드 - const excelDownload = useCallback(() => { - searchFacilityPrecipitation(ref.current, 1, 5000); - setIsInitExcelDownload(true); - }, [searchFacilityPrecipitation]); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + if (ref.current.partValue === 'A') { // 행정별 + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'R') { // 유역별 + if (!ref.current.regionValue || ref.current.regionValue === '0') { + alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.unitWaterShedValue || ref.current.unitWaterShedValue === '0') { + alert('엑셀 다운로드는 단위유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'G') { // 환경청별 + if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } + setIsExcelDownClick(true); + try { + await searchFacilityPrecipitation(ref.current, 1, 5000); + const latestExcelList = store.getState().SewageOccur.excelList || []; + if (latestExcelList.length > 0) { + const toNumber = (value: unknown) => { + if (value === null || value === undefined || value === '') return ''; + const parsed = typeof value === "number" ? value : Number(String(value).replace(/,/g, "")); + return Number.isFinite(parsed) ? parsed : ''; + }; + + const numericKeys = [ + "시설용량(m³/일)", + "물사용량(m³/일)", + "오수전환율(%)", + "오수발생량추정(m³/일)", + "연간하수유입량(m³/일)", + "청천시하수유입량(m³/일)", + ]; + + const excelData = latestExcelList.map((row) => { + const nextRow = { ...row } as Record<string, unknown>; + numericKeys.forEach((key) => { + if (key in nextRow) { + nextRow[key] = toNumber(nextRow[key]); + } + }); + return nextRow; + }); + + downloadExcelFormat({ + data: excelData as unknown as JSON[], + fileName: '오수발생량 추정', + sheetNameArray: ['오수발생량 추정'], + headerRange: 'A1:O1', + decimalArray0: [9, 10, 11, 12, 13, 14] + }); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFacilityPrecipitation]); // 페이지 변경 const handlePageChange = useCallback((page: number) => { @@ -211,8 +269,7 @@ export default function PlanSewage() { return ( <MainDiv> - <Title title="오수발생량 추정" /> - <ContentsDiv> + <ContentsDiv> <SearchBaseForm leftChildren={<> <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> @@ -230,6 +287,7 @@ export default function PlanSewage() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={false} @@ -250,7 +308,7 @@ export default function PlanSewage() { facilityCd={fcltyCd} searchYear={searchYear} />} - bodyClass="sm:max-w-[90vw] w-[1000px] h-[770px]" + bodyClass="sm:max-w-[90vw] w-[1000px] h-[100dvh] max-h-[770px]" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} noHeaderYn="Y" diff --git a/apps/fo/app/pages/(with-layout)/underWaterInput/modal/page.tsx b/apps/fo/app/pages/(with-layout)/underWaterInput/modal/page.tsx index 2e0f5f27e..02b1891ad 100644 --- a/apps/fo/app/pages/(with-layout)/underWaterInput/modal/page.tsx +++ b/apps/fo/app/pages/(with-layout)/underWaterInput/modal/page.tsx @@ -29,7 +29,7 @@ const tabList1 = [ export default function UnderWaterInputModal({fcltyCd='', fcltyName='',searchYear=new Date().getFullYear().toString()} : ModalProps) { // 그리드 관련 - const {Common, UnderWaterDetail, unserWaterModal} = useFoStore((state) => state); + const {Common, UnderWaterDetail, underWaterModal} = useFoStore((state) => state); const { btnClass } = Common; const [startYmDate, setStartYmDate] = useState<string>(searchYear+'01'); const [endYmDate, setEndYmDate] = useState<string>(searchYear+'12'); @@ -74,8 +74,8 @@ export default function UnderWaterInputModal({fcltyCd='', fcltyName='',searchYea }, [searchYear, tabTerm]); useEffect(()=>{ - unserWaterModal(fcltyCd, searchYear, searchYear+'01', searchYear+'12'); - }, [fcltyCd, searchYear, unserWaterModal]); + underWaterModal(fcltyCd, searchYear, searchYear+'01', searchYear+'12'); + }, [fcltyCd, searchYear, underWaterModal]); useEffect(() => { if(endYmDate < startYmDate) { @@ -93,8 +93,8 @@ export default function UnderWaterInputModal({fcltyCd='', fcltyName='',searchYea }, [startYmDate, endYmDate, gridListUnderWaterDetail, prevStartYmDate, prevEndYmDate]); useEffect(() => { - unserWaterModal(fcltyCd, searchYear, prevStartYmDate, prevEndYmDate); - } , [fcltyCd, searchYear, unserWaterModal, prevStartYmDate, prevEndYmDate]); + underWaterModal(fcltyCd, searchYear, prevStartYmDate, prevEndYmDate); + } , [fcltyCd, searchYear, underWaterModal, prevStartYmDate, prevEndYmDate]); const excelDownload = () => { const excelDownList: any[] = []; diff --git a/apps/fo/app/pages/(with-layout)/underWaterInput/page.tsx b/apps/fo/app/pages/(with-layout)/underWaterInput/page.tsx index 47e9ec912..383bca9be 100644 --- a/apps/fo/app/pages/(with-layout)/underWaterInput/page.tsx +++ b/apps/fo/app/pages/(with-layout)/underWaterInput/page.tsx @@ -7,11 +7,11 @@ */ "use client" -import { useFoStore } from "@/app/store"; +import { StoreContext, useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common"; -import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components"; +import { ContentsDiv, downloadExcelFormat, MainDiv, SearchBaseForm, SearchInput, SelectBox, UiModal, UiTable, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import UnderWaterInputModal from "./modal/page"; import type { UnderWaterProps } from "@/app/store/underWaterInput/types"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; @@ -35,6 +35,11 @@ export default function UnderWaterInput() { initializeCommonData: () => {}, searchUnderWaterList: () => {} }; + const store = useContext(StoreContext); + + if (!store) { + throw new Error("StoreContext is not available. Ensure StoreProvider is mounted."); + } const { isInit, @@ -50,6 +55,11 @@ export default function UnderWaterInput() { btnClass } = Common; + const defaultSearchYear = + selectSearchYear.find((item) => item.selected)?.val || + selectSearchYear[0]?.val || + "0"; + const { labelArray, } = UnderWaterSearch; @@ -60,7 +70,6 @@ export default function UnderWaterInput() { upHeadList3, underWaterList, underWaterCount, - excelList, isLoading, } = UnderWater; @@ -98,9 +107,9 @@ export default function UnderWaterInput() { // 시설상태 const [operationValue, setOperationValue] = useState<string>("0"); // 조회기간 - const [searchYear, setSearchYear] = useState<string>(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState<string>(defaultSearchYear); // 엑셀다운로드 트리거 - const [isMount, setIsMount] = useState<boolean>(false); + const [isExcelDownClick, setIsExcelDownClick] = useState<boolean>(false); /** * 페이지 함수 @@ -112,7 +121,7 @@ export default function UnderWaterInput() { // 상태값 설정 setFcltCpct(selectUpdownData[0]?.val || "0"); setOperationValue(selectOperationData[0]?.val || "0"); - setSearchYear(selectSearchYear[0]?.val || "0"); + setSearchYear(defaultSearchYear); // SearchInputSelect 초기화 setFcltyName(""); if (searchInputRef.current) { @@ -121,7 +130,7 @@ export default function UnderWaterInput() { input.value = ""; } } - searchUnderWaterList(1, 10, selectSearchYear[0]?.val || "0"); + searchUnderWaterList(1, 10, defaultSearchYear); } // 모달 닫기 @@ -144,31 +153,15 @@ export default function UnderWaterInput() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchUnderWaterList(1, 10, selectSearchYear[0]?.val || "0"); + searchUnderWaterList(1, 10, defaultSearchYear); } - }, [isInit, initializeCommonData, searchUnderWaterList, selectSearchYear]); - - useEffect(() => { - if(!isMount) return ; - - const timer = setTimeout(() => { - if (excelList && excelList.length > 0) { - downloadExcel({ - data: excelList as unknown as JSON[], - fileName: '지하수유입량 추정', - sheetNameArray: ['지하수유입량 추정'], - headerRange: 'A1:O1' - }); - } - }, 1000); // 약간의 지연을 두고 다운로드 실행 - return () => clearTimeout(timer); - }, [excelList, isMount]); + }, [isInit, initializeCommonData, searchUnderWaterList, defaultSearchYear]); /** * 콜백함수 */ // 조회 - const searchFacilityPrecipitation = useCallback(({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { + const searchFacilityPrecipitation = useCallback(async ({ partValue, regionValue, unitWaterShedValue, middleRegionValue, envPartValue, sidoValue, sigunValue }: ReturnType, pageNo: number, pageCnt:number=20) => { let nameCd, partStr switch (partValue) { case 'R': @@ -186,9 +179,10 @@ export default function UnderWaterInput() { break; } if (partStr.length > 1 || fcltCpct !== '0' || operationValue!== '0' ||nameCd ||(fcltyName.trim() !== "" && fcltyName.trim() !== null)) { - searchUnderWaterList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue, fcltyName.trim() !== "" || fcltyName.trim() !== null ? fcltyName : undefined); + await searchUnderWaterList(pageNo, pageCnt, searchYear, partStr, nameCd || '0', fcltCpct, operationValue + , fcltyName.trim() !== ""? fcltyName : undefined); } else { - searchUnderWaterList(pageNo, pageCnt, searchYear); + await searchUnderWaterList(pageNo, pageCnt, searchYear); } }, [fcltCpct, operationValue, fcltyName, searchUnderWaterList, searchYear]); @@ -199,10 +193,75 @@ export default function UnderWaterInput() { }, [upHeadList1, upHeadList2, upHeadList3]); // 엑셀다운로드 - const excelDownload = useCallback(() => { - searchFacilityPrecipitation(ref.current, 1, 5000); - setIsMount(true); - }, [searchFacilityPrecipitation]); + const excelDownload = useCallback(async () => { + if (isExcelDownClick) return; + if (ref.current.partValue === 'A') { // 행정별 + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'R') { // 유역별 + if (!ref.current.regionValue || ref.current.regionValue === '0') { + alert('엑셀 다운로드는 유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.unitWaterShedValue || ref.current.unitWaterShedValue === '0') { + alert('엑셀 다운로드는 단위유역 조건을 선택한 후 이용 가능합니다.'); + return; + } + } else if (ref.current.partValue === 'G') { // 환경청별 + if (!ref.current.envPartValue || ref.current.envPartValue === '0') { + alert('엑셀 다운로드는 환경청 조건을 선택한 후 이용 가능합니다.'); + return; + } + if (!ref.current.sidoValue || ref.current.sidoValue === '0') { + alert('엑셀 다운로드는 시도 조건을 선택한 후 이용 가능합니다.'); + return; + } + } + setIsExcelDownClick(true); + try { + await searchFacilityPrecipitation(ref.current, 1, 5000); + const latestExcelList = store.getState().UnderWater.excelList || []; + if (latestExcelList.length > 0) { + const toNumber = (value: unknown) => { + if (value === null || value === undefined || value === '') return ''; + const parsed = typeof value === "number" ? value : Number(String(value).replace(/,/g, "")); + return Number.isFinite(parsed) ? parsed : ''; + }; + + const numericKeys = [ + "시설용량(m³/일)", + "평균유입하수량(m³/일)", + "연간지하수유입량(m³/일)", + "하절기지하수유입량(m³/일)", + "동절기지하수유입량(m³/일)", + ]; + + const excelData = latestExcelList.map((row) => { + const nextRow = { ...row } as Record<string, unknown>; + numericKeys.forEach((key) => { + if (key in nextRow) { + nextRow[key] = toNumber(nextRow[key]); + } + }); + return nextRow; + }); + + downloadExcelFormat({ + data: excelData as unknown as JSON[], + fileName: '지하수유입량 추정', + sheetNameArray: ['지하수유입량 추정'], + headerRange: 'A1:O1', + decimalArray0: [9, 10, 11, 12, 13] + }); + } else { + alert("조회된 결과가 없습니다."); + } + } finally { + setIsExcelDownClick(false); + } + }, [isExcelDownClick, searchFacilityPrecipitation, store]); // 페이지 변경 const handlePageChange = useCallback((page: number) => { @@ -215,8 +274,7 @@ export default function UnderWaterInput() { return ( <MainDiv> - <Title title="지하수유입량 추정" /> - <ContentsDiv> + <ContentsDiv> <SearchBaseForm leftChildren={<> <SelectBox className="col-span-3" label={labelArray[3] as string} selectArray={selectUpdownData} onSelectValue={(val) => setFcltCpct(val.toString())} value={fcltCpct} selectClass={undefined} /> @@ -234,6 +292,7 @@ export default function UnderWaterInput() { btnClass={btnClass} queryClick={chkVal} excelClick={excelDownload} + excelDisabled={isExcelDownClick} initClick={resetVal} ref={ref} isAdmin={false} @@ -251,7 +310,7 @@ export default function UnderWaterInput() { {selectedRow !== null && ( <UiModal body={<UnderWaterInputModal fcltyCd={fcltyCd} fcltyName={fcltyNm} searchYear={searchYear}></UnderWaterInputModal>} - bodyClass="sm:max-w-[90vw] w-[1000px] h-[770px]" + bodyClass="sm:max-w-[90vw] w-[1000px] h-[100dvh] max-h-[770px]" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} noHeaderYn="Y" @@ -260,4 +319,4 @@ export default function UnderWaterInput() { )} </MainDiv> ); -} \ No newline at end of file +} diff --git a/apps/fo/app/store/common/index.tsx b/apps/fo/app/store/common/index.tsx index e4aebc982..f71550f1a 100644 --- a/apps/fo/app/store/common/index.tsx +++ b/apps/fo/app/store/common/index.tsx @@ -12,6 +12,8 @@ import { getFetch, getFetchAreaList } from "@/app/components/api"; export type SelectDataType = { text: string; val: string; + selected?: boolean; + hidden?: boolean; } // grid header export type TableUpperProps = { @@ -129,14 +131,14 @@ const selectEnvPartSigunData: SelectDataType[] = [ // ]; // 상향 하향 데이터 -const selectUpdownData:SelectDataType[] = [ +const selectUpdownData: SelectDataType[] = [ { text: "전체", val: '0' }, { text: "500미만", val: '400' }, { text: "500이상", val: '500' }, ]; // 운영 상태 데이터 -const selectOperationData:SelectDataType[] = [ +const selectOperationData: SelectDataType[] = [ { text: "전체", val: '0' }, { text: "가동중", val: '1' }, { text: "재가동", val: '2' }, @@ -145,7 +147,7 @@ const selectOperationData:SelectDataType[] = [ ]; // 시설 구분 데이터 -const selectFacilityPartData:SelectDataType[] = [ +const selectFacilityPartData: SelectDataType[] = [ { text: "전체", val: '0' }, { text: "공공하수처리시설", val: 'SW' }, { text: "오수처리장", val: 'SS' }, @@ -158,25 +160,29 @@ const selectFacilityPartData:SelectDataType[] = [ // 년도 데이터 const getYearData = () => { - const year = new Date().getFullYear(); - const year1 = year - 1; - const year2 = year - 2; - const year3 = year - 3; - const year4 = year - 4; - const year5 = year - 5; - return [ - { text: year1 + "년", val: year1.toString() }, - { text: year2 + "년", val: year2.toString() }, - { text: year3 + "년", val: year3.toString() }, - { text: year4 + "년", val: year4.toString() }, - { text: year5 + "년", val: year5.toString() }, - ] + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth() + 1; + + // 상반기(6월까지)는 2년 전, 하반기는 1년 전부터 시작 + const defaultIdx = month <= 6 ? 2 : 1; + const startYear = year; + + // 최근 10개년도 제공 (기본값: 첫 번째 항목 선택) + return Array.from({ length: 10 }, (_, index) => { + const targetYear = startYear - index; + return { + text: `${targetYear}년`, + val: targetYear.toString(), + selected: index === defaultIdx, + }; + }); } -const selectSearchYear:SelectDataType[] = getYearData(); +const selectSearchYear: SelectDataType[] = getYearData(); // 유량 기간 구분 -const selectFlowTerm : SelectDataType[] = [ +const selectFlowTerm: SelectDataType[] = [ { text: "연간", val: '0' }, { text: "하절기", val: '01' }, { text: "동절기", val: '02' }, @@ -185,15 +191,15 @@ const selectFlowTerm : SelectDataType[] = [ ]; // 유량 기간 구분 -const selectFlowPanel : SelectDataType[] = [ - { text: "전체", val: '0'}, - { text: "3~10mm미만", val: '2'}, - { text: "10~20mm미만", val: '3'}, - { text: "20~30mm미만", val: '4'}, - { text: "30~40mm미만", val: '5'}, - { text: "40~50mm미만", val: '6'}, - { text: "50~60mm미만", val: '7'}, - { text: "60mm이상", val: '8'}, +const selectFlowPanel: SelectDataType[] = [ + { text: "전체", val: '0' }, + { text: "3~10mm미만", val: '2' }, + { text: "10~20mm미만", val: '3' }, + { text: "20~30mm미만", val: '4' }, + { text: "30~40mm미만", val: '5' }, + { text: "40~50mm미만", val: '6' }, + { text: "50~60mm미만", val: '7' }, + { text: "60mm이상", val: '8' }, ]; // 중권역 데이터 @@ -220,9 +226,9 @@ export const roundUp = (value: number, precision: number = 1): number => { * @param day : 일수 * @param digit : 소수점 자리수 */ -export const qltyDataSetting = (value: number, day : number, digit: number) => { - const toZero = digit === 1 ? '0.0' : digit === 2 ? '0.00' : '0.000'; - return value > 0 ? roundUp((value/day), digit).toFixed(digit) : toZero; +export const qltyDataSetting = (value: number, day: number, digit: number) => { + const toZero = digit === 1 ? '0.0' : digit === 2 ? '0.00' : '0.000'; + return value > 0 ? roundUp((value / day), digit).toFixed(digit) : toZero; }; export const COD_YEAR = 2021; @@ -234,13 +240,13 @@ export const COD_YEAR = 2021; // 처리시설 계획정보 호출 (알에스원) export const planInfoUrl = '/dc-service/api/v1/nowDmgMtncGnsvy'; -export const fetchPlanInfo = async (targetYear : string | undefined, ctpvNm : string | undefined, sggNm : string | undefined, fcltyCd : string | undefined) => { +export const fetchPlanInfo = async (targetYear: string | undefined, ctpvNm: string | undefined, sggNm: string | undefined, fcltyCd: string | undefined) => { try { const baseUrl = planInfoUrl; const queryParams = new URLSearchParams(); if (ctpvNm && ctpvNm !== '전체') queryParams.append('ctpvNm', ctpvNm); if (sggNm && sggNm !== '전체') queryParams.append('sggNm', sggNm); - if (targetYear) queryParams.append('targetYear',targetYear); + if (targetYear) queryParams.append('targetYear', targetYear); if (fcltyCd) queryParams.append('swtrmtFcltCd', fcltyCd); const queryString = queryParams.toString(); const url = queryString ? `${baseUrl}?${queryString}` : baseUrl; @@ -258,16 +264,16 @@ const fetchSidoData = async (): Promise<SelectDataType[]> => { const res = await getFetch('fo', '/addr/city'); const data = res.data; selectSidoData = [ - { text: "전체", val: '0'}, + { text: "전체", val: '0' }, ...data.map((item: sidoDataType) => ({ - text: item.sido || '', - val: String(item.adstDstrCd) || '' + text: item.sido || '', + val: String(item.adstDstrCd) || '' })) ]; return selectSidoData; } catch (error) { - console.error('시도 데이터를 가져오는데 실패했습니다:', error); - return selectSidoData; + console.error('시도 데이터를 가져오는데 실패했습니다:', error); + return selectSidoData; } }; @@ -279,10 +285,10 @@ const fetchSigunData = async (): Promise<SelectDataType[]> => { selectSigunData = [ { text: "전체", val: '0' }, ...data - .map((item: sigunDataType) => ({ + .map((item: sigunDataType) => ({ text: item.gugun || '', val: String(item.adstDstrCd) || '' - })) + })) ]; return selectSigunData; } catch (error) { @@ -297,7 +303,7 @@ const fetchRegionData = async (): Promise<SelectDataType[]> => { const res = await getFetch('fo', '/addr/sarea'); const data = res.data; return [ - { text: "전체", val: '0'}, + { text: "전체", val: '0' }, ...data.map((item: regionDataType) => ({ text: item.rvbsn || '', val: String(item.rvbsnCd) || '' @@ -317,10 +323,10 @@ const fetchMiddleRegionData = async (): Promise<SelectDataType[]> => { return [ { text: "전체", val: '0' }, ...data - .map((item: middleRegionDataType) => ({ - text: item.mdmbsn || '', - val: String(item.rvbsnCd) || '' - })) + .map((item: middleRegionDataType) => ({ + text: item.mdmbsn || '', + val: String(item.rvbsnCd) || '' + })) ]; } catch (error) { console.error('중권역 데이터를 가져오는데 실패했습니다:', error); @@ -336,10 +342,10 @@ const fetchUnitWaterShedData = async (): Promise<SelectDataType[]> => { return [ { text: "전체", val: '0' }, ...data - .map((item: unitWaterShedDataType) => ({ - text: item.lgbsn || '', - val: String(item.rvbsnCd) || '' - })) + .map((item: unitWaterShedDataType) => ({ + text: item.lgbsn || '', + val: String(item.rvbsnCd) || '' + })) ]; } catch (error) { console.error('단위유역 데이터를 가져오는데 실패했습니다:', error); @@ -355,11 +361,11 @@ const fetchEnvPartData = async (): Promise<envPartSelectDataType[]> => { selectEnvPartData = [ { text: "전체", val: '0', adstDliStrCd: '' }, ...data - .map((item: envPartDataType) => ({ - text: item.lclgvrnmntNm || '', - val: String(item.lclgvrnmntCd) || '', - adstDliStrCd: item.adstDliStrCd || '' - })) + .map((item: envPartDataType) => ({ + text: item.lclgvrnmntNm || '', + val: String(item.lclgvrnmntCd) || '', + adstDliStrCd: item.adstDliStrCd || '' + })) ]; return selectEnvPartData; } catch (error) { @@ -372,7 +378,7 @@ const fetchEnvPartData = async (): Promise<envPartSelectDataType[]> => { * Store - expaort ====================================*/ export type CommonAction = { - getMenuAuth : (menuCd : number) => Promise<void>; + getMenuAuth: (menuCd: number) => Promise<void>; initializeCommonData: () => Promise<void>; } @@ -394,7 +400,7 @@ export type CommonType = { selectFlowPanel: SelectDataType[]; selectEnvPartData: envPartSelectDataType[]; btnClass: string; - COD_YEAR : number; + COD_YEAR: number; }; auth: { @@ -424,7 +430,7 @@ export const commonState: CommonType = { selectFlowPanel: selectFlowPanel, selectEnvPartData: selectEnvPartData, btnClass: "p-4 m-2 !mr-10 text-white rounded-sm text-lg font-medium", - COD_YEAR : COD_YEAR, + COD_YEAR: COD_YEAR, }, auth: { @@ -436,10 +442,12 @@ export const commonState: CommonType = { }, }; -export const CommonReducer:(set:(fn: (state: CommonType) => CommonType) => void)=>CommonAction=(set: (fn: (state: CommonType) => CommonType) => void) => { +export const CommonReducer = ( + set: (fn: (state: CommonType) => CommonType) => void +): CommonAction => { return { // 메뉴 권한 확인 - getMenuAuth : async (menuCd : number) =>{ + getMenuAuth: async (menuCd: number) => { const response = await getFetch( "local", `/cms-service/api/v1/menu-authrts/pageinfo/${menuCd}` @@ -478,4 +486,4 @@ export const CommonReducer:(set:(fn: (state: CommonType) => CommonType) => void) } } -export type CommonStore = CommonType & CommonAction; \ No newline at end of file +export type CommonStore = CommonType & CommonAction; diff --git a/apps/fo/app/store/dashboard/index.tsx b/apps/fo/app/store/dashboard/index.tsx index b5f678a22..398f82c0e 100644 --- a/apps/fo/app/store/dashboard/index.tsx +++ b/apps/fo/app/store/dashboard/index.tsx @@ -5,6 +5,7 @@ import { getFetch } from "@/app/components/api"; import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import DataForm, { roundNumber } from "@/app/components/common/DataForm"; +// import { getDaysInMonth } from "@/app/components/common/DataForm"; import { fetchPlanInfo, OSU_PERCENT, qltyDataSetting, roundUp } from "../common"; import { type FacilityWaterQltyProps } from "../facilityWaterQlty"; import { fetchFlowRateModalList } from "../flowRate"; @@ -22,93 +23,93 @@ export type TableUpperProps = { title: string; upName?: string; upSequnce?: number; - align? : string; - row? : number; + align?: string; + row?: number; rowSpan?: number; colSpan?: number; - widthPercent? : number; + widthPercent?: number; } // ===TAB1 // main fclty export type mainFcltyProps = { - fcltyNm : string, - facilityCapacity : string, - publicMethod : string, - location : string + fcltyNm: string, + facilityCapacity: string, + publicMethod: string, + location: string } // 시설계획 export type planFcltyListProps = { - item : string, - provisionalApproval : string | number | '', - now : string | number | '', - stepOne : string | number | '', - stepTwo : string | number | '', - stepThree : string | number | '', - stepFour : string | number | '', + item: string, + provisionalApproval: string | number | '', + now: string | number | '', + stepOne: string | number | '', + stepTwo: string | number | '', + stepThree: string | number | '', + stepFour: string | number | '', } // 운영실태 export type OperationalStatusListProps = { - flowRate : string, - waterQlty : string, - yearAvg : number | '', - yearMax : number | '', - yearMin : number | '', - summerAll : number | string | '', - summerThree : number | string | '', - summerRain : number | string | '', - winterAll : number | string | '', - winterSunnyAvg : number | string | '', - winterSunnyMax : number | string | '', - winterSunnyMin : number | string | '', - groundWaterAvg : number | string | '', - groundWaterSummer : number | string | '', - groundWaterWinter : number | string | '', - rdi : number | string | '', - rdiRain : number | string | '', + flowRate: string, + waterQlty: string, + yearAvg: number | '', // 평균 + yearMax: number | '', // 최대 + yearMin: number | '', // 최소 + summerAll: number | string | '', // 하절기 전체 + summerThree: number | string | '', // 하절기 3mm이상 + summerRain: number | string | '', // 하절기 강우 + winterAll: number | string | '', // 동절기 전체 + winterSunnyAvg: number | string | '', // 동절기 청천시 평균 + winterSunnyMax: number | string | '', // 동절기 청천시 최대 + winterSunnyMin: number | string | '', // 동절기 청천시 최소 + groundWaterAvg: number | string | '', // 지하수 유입량 연평균 + groundWaterSummer: number | string | '',// 지하수 유입량 하절기 평균 + groundWaterWinter: number | string | '',// 지하수 유입량 동절기 평균 + rdi: number | string | '', // RDII 추정 + rdiRain: number | string | '', // RDII 추정시 일강수량(mm) } export interface operationalStatusListProps { - flowRate : string, - waterQlty : string, - yearAvg : number | '', - yearMax : number | '', - yearMin : number | '', - summerAll : number | '', - summerThree : number | '', - summerRain : number | '', - winterAll : number | '', - winterSunnyAvg : number | '', - winterSunnyMax : number | '', - winterSunnyMin : number | '', - groundWaterAvg : number | string | '', - groundWaterSummer : number | string | '', - groundWaterWinter : number | string | '', - rdi : number | '', + flowRate: string, + waterQlty: string, + yearAvg: number | '', + yearMax: number | '', + yearMin: number | '', + summerAll: number | '', + summerThree: number | '', + summerRain: number | '', + winterAll: number | '', + winterSunnyAvg: number | '', + winterSunnyMax: number | '', + winterSunnyMin: number | '', + groundWaterAvg: number | string | '', + groundWaterSummer: number | string | '', + groundWaterWinter: number | string | '', + rdi: number | '', } // 차트 export type lineBarChartDataProps = { - label : string, - lineVal : number - barVal : number - barVal0 : number - barVal1 : number - lineVal0 : number - lineVal1 : number + label: string, + lineVal: number + barVal: number + barVal0: number + barVal1: number + lineVal0: number + lineVal1: number } export type lineChartDataProps = { - label : string, - val : number, + label: string, + val: number, } export interface RainManageListProps { - rainItem : string, - rainRate : string, - rainDay : number, + rainItem: string, + rainRate: string, + rainDay: number, bscFctCd?: string, bscFctNm?: string, msrDt?: string @@ -126,10 +127,10 @@ export interface RainManageListProps { efflWqltSs: number | '', efflWqltTn: number | '', efflWqltTp: number | '', - rgnNm?:string|'', - rvbsn?:string|'', - lgbsn?:string|'', - mdmbsn?:string|'', + rgnNm?: string | '', + rvbsn?: string | '', + lgbsn?: string | '', + mdmbsn?: string | '', inflAmnt: number | '', lclgvrnmntNm?: string, infoRnDay?: number | '', @@ -149,7 +150,7 @@ export interface RainManageListProps { dsInflWqltTp: number | '', extnsSeqno?: number | '', rainEffectDay?: number | '', - toBeInfoRnDay? : number | '', + toBeInfoRnDay?: number | '', } export interface RainManageExcelType { @@ -157,7 +158,7 @@ export interface RainManageExcelType { 강우조건비율: number | '', 일수: number | '', 유입유량: string | number | '', - 시설용량대비: string |number | '', + 시설용량대비: string | number | '', 설계유입수질BOD: string | number | '', 설계유입수질TOC: string | number | '', 설계유입수질SS: string | number | '', @@ -169,840 +170,840 @@ export interface RainManageExcelType { 유입수질TN: string | number | '', 유입수질TP: string | number | '', 방류수질BOD: string | number | '', - 방류수질TOC: string |number | '', + 방류수질TOC: string | number | '', 방류수질SS: string | number | '', 방류수질TN: string | number | '', 방류수질TP: string | number | '', } export interface DrainageEquipmentListProps { - part1 : string | '', - part2 : string | '', - nowSum : number | '', - nowJoin : number | '', - nowSep : number | '', - stepOneSum : number | '', - stepOneJoin : number | '', - stepOneSep : number | '', - stepTwoSum : number | '', - stepTwoJoin : number | '', - stepTwoSep : number | '', - stepThreeSum : number | '', - stepThreeJoin : number | '', - stepThreeSep : number | '', - stepFourSum : number | '', - stepFourJoin : number | '', - stepFourSep : number | '', - etc : string | '', + part1: string | '', + part2: string | '', + nowSum: number | '', + nowJoin: number | '', + nowSep: number | '', + stepOneSum: number | '', + stepOneJoin: number | '', + stepOneSep: number | '', + stepTwoSum: number | '', + stepTwoJoin: number | '', + stepTwoSep: number | '', + stepThreeSum: number | '', + stepThreeJoin: number | '', + stepThreeSep: number | '', + stepFourSum: number | '', + stepFourJoin: number | '', + stepFourSep: number | '', + etc: string | '', } export interface DrainageEquipmentExcelType { - partOne : string | '', - partTwo : string | '', - nowSum : number | '', - nowJoin : number | '', - nowSep : number | '', - stepOneSum : number | '', - stepOneJoin : number | '', - stepOneSep : number | '', - stepTwoSum : number | '', - stepTwoJoin : number | '', - stepTwoSep : number | '', - stepThreeSum : number | '', - stepThreeJoin : number | '', - stepThreeSep : number | '', - stepFourSum : number | '', - stepFourJoin : number | '', - stepFourSep : number | '', + partOne: string | '', + partTwo: string | '', + nowSum: number | '', + nowJoin: number | '', + nowSep: number | '', + stepOneSum: number | '', + stepOneJoin: number | '', + stepOneSep: number | '', + stepTwoSum: number | '', + stepTwoJoin: number | '', + stepTwoSep: number | '', + stepThreeSum: number | '', + stepThreeJoin: number | '', + stepThreeSep: number | '', + stepFourSum: number | '', + stepFourJoin: number | '', + stepFourSep: number | '', } +// type WtrUsgYmProps = { +// bscFctCd: string, // 처리구역시설코드 +// levyYm: string, // 부과년월 +// wtrUsg: number, // 월별 물사용량(m³/월) +// } /** * Store - data */ // Main -const DashBoardTopLabelArray:string[] = ['구분', '시도', '시군구', '년도', '시설명']; +const DashBoardTopLabelArray: string[] = ['구분', '시도', '시군구', '년도', '시설명']; // === Tab1 // grid header -const upHeadListFclty:TableUpperProps[] = [ - { id : "fcltyNm", title : "시설명"}, - { id : "facilityCapacity", title : "시설용량(m³/일)"}, - { id : "publicMethod", title : "공법"}, - { id : "location", title : "지역구"}, +const upHeadListFclty: TableUpperProps[] = [ + { id: "fcltyNm", title: "시설명" }, + { id: "facilityCapacity", title: "시설용량(m³/일)" }, + { id: "publicMethod", title: "공법" }, + { id: "location", title: "지역구" }, ] -const fcltyList : mainFcltyProps[] = [ +const fcltyList: mainFcltyProps[] = [ ] function getPlanYearList(): string[] { - const currentYear = new Date().getFullYear(); // 올해 - const startYear = currentYear - 4; // 5년 전 - const endYear = currentYear + 1; // 내년 + const currentYear = new Date().getFullYear(); // 올해 + const startYear = currentYear - 4; // 5년 전 + const endYear = currentYear + 1; // 내년 - const years: string[] = []; + const years: string[] = []; - for (let y = startYear; y <= endYear; y++) { - years.push(String(y)); // 혹은 y.toString() - } + for (let y = startYear; y <= endYear; y++) { + years.push(String(y)); // 혹은 y.toString() + } - return years; + return years; } -let planYearList : string[] = getPlanYearList(); +let planYearList: string[] = getPlanYearList(); -const upHeadListPlanFclty : TableUpperProps[] = [ - { id: "item", title: "", upName:"upChangeRe", upSequnce:1 }, - { id: "provisionalApproval", title: "기승인\n4단계", upName:"upChangeRe", upSequnce:2 }, - { id: "now", title: "현재", upName:"upChangeRe", upSequnce:3 }, - { id: "stepOne", title: "1단계", upName:"upChangeRe", upSequnce:4 }, - { id: "stepTwo", title: "2단계", upName:"upChangeRe", upSequnce:5 }, - { id: "stepThree", title: "3단계", upName:"upChangeRe", upSequnce:6 }, - { id: "stepFour", title: "4단계", upName:"upChangeRe", upSequnce:7 }, +const upHeadListPlanFclty: TableUpperProps[] = [ + { id: "item", title: "", upName: "upChangeRe", upSequnce: 1 }, + { id: "provisionalApproval", title: "기승인\n4단계", upName: "upChangeRe", upSequnce: 2 }, + { id: "now", title: "현재", upName: "upChangeRe", upSequnce: 3 }, + { id: "stepOne", title: "1단계", upName: "upChangeRe", upSequnce: 4 }, + { id: "stepTwo", title: "2단계", upName: "upChangeRe", upSequnce: 5 }, + { id: "stepThree", title: "3단계", upName: "upChangeRe", upSequnce: 6 }, + { id: "stepFour", title: "4단계", upName: "upChangeRe", upSequnce: 7 }, ] // 시설계획 좌측 상단 -const planFcltyList:planFcltyListProps[] = [ +const planFcltyList: planFcltyListProps[] = [ { - item : "1.증설계획\n(㎥/일)", + item: "1.증설계획\n(㎥/일)", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "2.처리인구(인)", + item: "2.처리인구(인)", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "3.처리구역\n(km²)", + item: "3.처리구역\n(km²)", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "4.분류식화율\n(%)", + item: "4.분류식화율\n(%)", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "5.물사용량", + item: "5.물사용량", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "년도\n(최근 3개년)", + item: "년도\n(최근 3개년)", provisionalApproval: "물사용량\n(㎥/일)", now: "", stepOne: "", stepTwo: "비고", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "2021", + item: "2021", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "2022", + item: "2022", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, { - item : "2023", + item: "2023", provisionalApproval: "", now: "", stepOne: "", stepTwo: "", stepThree: "", - stepFour : "", + stepFour: "", }, ] -const upHeadListOperationStatus : TableUpperProps[] = [ - { id: "flowRate", title: "유량 및 수질", upName:"upChangeRe1", upSequnce:1 }, - { id: "waterQlty", title: "유량 및 수질", upName:"upChangeRe1", upSequnce:2 }, - { id: "yearAvg", title: "평균", upName:"upChangeRe2", upSequnce:1 }, - { id: "yearMax", title: "최대", upName:"upChangeRe2", upSequnce:2 }, - { id: "yearMin", title: "최소", upName:"upChangeRe2", upSequnce:3 }, - { id: "summerAll", title: "전기간", upName:"upChangeRe3", upSequnce:1 }, - { id: "summerThree", title: "강우시\n(3mm이상)", upName:"upChangeRe3", upSequnce:2 }, - { id: "summerRain", title: "청천시", upName:"upChangeRe3", upSequnce:3 }, - { id: "winterAll", title: "전기간", upName:"upChangeRe4", upSequnce:1 }, - { id: "winterSunnyAvg", title: "청천시\n평균", upName:"upChangeRe4", upSequnce:2 }, - { id: "winterSunnyMax", title: "청천시\n최대", upName:"upChangeRe4", upSequnce:3 }, - { id: "winterSunnyMin", title: "청천시\n최소", upName:"upChangeRe4", upSequnce:4 }, - { id: "groundWaterAvg", title: "연평균", upName:"upChangeRe5", upSequnce:1 }, - { id: "groundWaterSummer", title: "하절기\n평균", upName:"upChangeRe5", upSequnce:2 }, - { id: "groundWaterWinter", title: "동절기\n평균", upName:"upChangeRe5", upSequnce:3 }, +const upHeadListOperationStatus: TableUpperProps[] = [ // 시설정보 및 운영현황 + { id: "flowRate", title: "유량 및 수질", upName: "upChangeRe1", upSequnce: 1 }, + { id: "waterQlty", title: "유량 및 수질", upName: "upChangeRe1", upSequnce: 2 }, + { id: "yearAvg", title: "평균", upName: "upChangeRe2", upSequnce: 1 }, + { id: "yearMax", title: "최대", upName: "upChangeRe2", upSequnce: 2 }, + { id: "yearMin", title: "최소", upName: "upChangeRe2", upSequnce: 3 }, + { id: "summerAll", title: "전기간", upName: "upChangeRe3", upSequnce: 1 }, + { id: "summerThree", title: "강우시\n(3mm이상)", upName: "upChangeRe3", upSequnce: 2 }, + { id: "summerRain", title: "청천시", upName: "upChangeRe3", upSequnce: 3 }, + { id: "winterAll", title: "전기간", upName: "upChangeRe4", upSequnce: 1 }, + { id: "winterSunnyAvg", title: "청천시\n평균", upName: "upChangeRe4", upSequnce: 2 }, + { id: "winterSunnyMax", title: "청천시\n최대", upName: "upChangeRe4", upSequnce: 3 }, + { id: "winterSunnyMin", title: "청천시\n최소", upName: "upChangeRe4", upSequnce: 4 }, + { id: "groundWaterAvg", title: "연평균", upName: "upChangeRe5", upSequnce: 1 }, + { id: "groundWaterSummer", title: "하절기\n평균", upName: "upChangeRe5", upSequnce: 2 }, + { id: "groundWaterWinter", title: "동절기\n평균", upName: "upChangeRe5", upSequnce: 3 }, { id: "rdi", title: "RDII 추정" }, { id: "rdiRain", title: "RDII 추정시\n일강수량(mm)" }, ] -const upHeadListRainManage : TableUpperProps[] = [ - { id: "rainItem", title: "강우량", row:0, rowSpan:2, widthPercent: 8 }, - { id: "rainRate", title: "강우조건\n비율(%)", row:0, rowSpan:2, align: "right", widthPercent: 5 }, - { id: "rainDay", title: "일수", row:0, rowSpan:2, align: "right", widthPercent: 5 }, - { id: "A", title: "유입(㎥/일)", row:0, colSpan:2 }, - { id: "inflAmnt", title: "유입유량", row:1, align: "right", widthPercent: 6 }, - { id: "fctCpc", title: "시설용량대비", row:1, align: "right", widthPercent: 6 }, - { id: "B", title: "설계유입수질(㎎/L)", row:0, colSpan:5 }, - { id: "dsInflWqltBod", title: "BOD", row:1, align: "right" }, - { id: "dsInflWqltCod", title: "TOC\n(COD)", row:1, align: "right" }, - { id: "dsInflWqltSs", title: "SS", row:1, align: "right" }, - { id: "dsInflWqltTn", title: "T-N", row:1, align: "right" }, - { id: "dsInflWqltTp", title: "T-P", row:1, align: "right" }, - { id: "C", title: "유입수질(㎎/L)", row:0, colSpan:5 }, - { id: "inflAssctBfrWqltBod", title: "BOD", row:1, align: "right" }, - { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)", row:1, align: "right" }, - { id: "inflAssctBfrWqltSs", title: "SS", row:1, align: "right" }, - { id: "inflAssctBfrWqltTn", title: "T-N", row:1, align: "right" }, - { id: "inflAssctBfrWqltTp", title: "T-P", row:1, align: "right" }, - { id: "D", title: "방류수질(㎎/L)", row:0, colSpan:5 }, - { id: "efflWqltBod", title: "BOD", row:1, align: "right" }, - { id: "efflWqltToc", title: "TOC\n(COD)", row:1, align: "right" }, - { id: "efflWqltSs", title: "SS", row:1, align: "right" }, - { id: "efflWqltTn", title: "T-N", row:1, align: "right" }, - { id: "efflWqltTp", title: "T-P", row:1, align: "right" }, +const upHeadListRainManage: TableUpperProps[] = [ // 강우일 운영현황 + { id: "rainItem", title: "강우량", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "rainRate", title: "강우조건\n비율(%)", row: 0, rowSpan: 2, align: "right", widthPercent: 5 }, + { id: "rainDay", title: "일수", row: 0, rowSpan: 2, align: "right", widthPercent: 5 }, + { id: "A", title: "유입(㎥/일)", row: 0, colSpan: 2 }, + { id: "inflAmnt", title: "유입유량", row: 1, align: "right", widthPercent: 6 }, + { id: "fctCpc", title: "시설용량대비", row: 1, align: "right", widthPercent: 6 }, + { id: "B", title: "설계유입수질(㎎/L)", row: 0, colSpan: 5 }, + { id: "dsInflWqltBod", title: "BOD", row: 1, align: "right" }, + { id: "dsInflWqltCod", title: "TOC\n(COD)", row: 1, align: "right" }, + { id: "dsInflWqltSs", title: "SS", row: 1, align: "right" }, + { id: "dsInflWqltTn", title: "T-N", row: 1, align: "right" }, + { id: "dsInflWqltTp", title: "T-P", row: 1, align: "right" }, + { id: "C", title: "유입수질(㎎/L)", row: 0, colSpan: 5 }, + { id: "inflAssctBfrWqltBod", title: "BOD", row: 1, align: "right" }, + { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)", row: 1, align: "right" }, + { id: "inflAssctBfrWqltSs", title: "SS", row: 1, align: "right" }, + { id: "inflAssctBfrWqltTn", title: "T-N", row: 1, align: "right" }, + { id: "inflAssctBfrWqltTp", title: "T-P", row: 1, align: "right" }, + { id: "D", title: "방류수질(㎎/L)", row: 0, colSpan: 5 }, + { id: "efflWqltBod", title: "BOD", row: 1, align: "right" }, + { id: "efflWqltToc", title: "TOC\n(COD)", row: 1, align: "right" }, + { id: "efflWqltSs", title: "SS", row: 1, align: "right" }, + { id: "efflWqltTn", title: "T-N", row: 1, align: "right" }, + { id: "efflWqltTp", title: "T-P", row: 1, align: "right" }, ] -const upHeadListDrainageEquipment : TableUpperProps[] = [ - { id: "part1", title: "구분", row:0, upName:"upChangeRe1", upSequnce:1 }, - { id: "part2", title: "구분", row:0, upName:"upChangeRe1", upSequnce:2 }, - { id: "nowSum", title: "계", row:2, upName:"upChangeRe2", upSequnce:1 }, - { id: "nowJoin", title: "합류식\n가구수", row:2, upName:"upChangeRe2", upSequnce:2 }, - { id: "nowSep", title: "분리식\n가구수", row:2, upName:"upChangeRe2", upSequnce:3 }, - { id: "stepOneSum", title: "계", row:2, upName:"upChangeRe3", upSequnce:1 }, - { id: "stepOneJoin", title: "합류식\n가구수", row:2, upName:"upChangeRe3", upSequnce:2 }, - { id: "stepOneSep", title: "분리식\n가구수", row:2, upName:"upChangeRe3", upSequnce:3 }, - { id: "stepTwoSum", title: "계", row:2, upName:"upChangeRe4", upSequnce:1 }, - { id: "stepTwoJoin", title: "합류식\n가구수", row:2, upName:"upChangeRe4", upSequnce:2 }, - { id: "stepTwoSep", title: "분리식\n가구수", row:2, upName:"upChangeRe4", upSequnce:3 }, - { id: "stepThreeSum", title: "계", row:2, upName:"upChangeRe5", upSequnce:1 }, - { id: "stepThreeJoin", title: "합류식\n가구수", row:2, upName:"upChangeRe5", upSequnce:2 }, - { id: "stepThreeSep", title: "분리식\n가구수", row:2, upName:"upChangeRe5", upSequnce:3 }, - { id: "stepFourSum", title: "계", row:2, upName:"upChangeRe6", upSequnce:1 }, - { id: "stepFourJoin", title: "합류식\n가구수", row:2, upName:"upChangeRe6", upSequnce:2 }, - { id: "stepFourSep", title: "분리식\n가구수", row:2, upName:"upChangeRe6", upSequnce:3 }, - { id: "etc", title: "비고", row:0, upName:"upChangeRe7", upSequnce:1 }, +const upHeadListDrainageEquipment: TableUpperProps[] = [ + { id: "part1", title: "구분", row: 0, upName: "upChangeRe1", upSequnce: 1 }, + { id: "part2", title: "구분", row: 0, upName: "upChangeRe1", upSequnce: 2 }, + { id: "nowSum", title: "계", row: 2, upName: "upChangeRe2", upSequnce: 1 }, + { id: "nowJoin", title: "합류식\n가구수", row: 2, upName: "upChangeRe2", upSequnce: 2 }, + { id: "nowSep", title: "분리식\n가구수", row: 2, upName: "upChangeRe2", upSequnce: 3 }, + { id: "stepOneSum", title: "계", row: 2, upName: "upChangeRe3", upSequnce: 1 }, + { id: "stepOneJoin", title: "합류식\n가구수", row: 2, upName: "upChangeRe3", upSequnce: 2 }, + { id: "stepOneSep", title: "분리식\n가구수", row: 2, upName: "upChangeRe3", upSequnce: 3 }, + { id: "stepTwoSum", title: "계", row: 2, upName: "upChangeRe4", upSequnce: 1 }, + { id: "stepTwoJoin", title: "합류식\n가구수", row: 2, upName: "upChangeRe4", upSequnce: 2 }, + { id: "stepTwoSep", title: "분리식\n가구수", row: 2, upName: "upChangeRe4", upSequnce: 3 }, + { id: "stepThreeSum", title: "계", row: 2, upName: "upChangeRe5", upSequnce: 1 }, + { id: "stepThreeJoin", title: "합류식\n가구수", row: 2, upName: "upChangeRe5", upSequnce: 2 }, + { id: "stepThreeSep", title: "분리식\n가구수", row: 2, upName: "upChangeRe5", upSequnce: 3 }, + { id: "stepFourSum", title: "계", row: 2, upName: "upChangeRe6", upSequnce: 1 }, + { id: "stepFourJoin", title: "합류식\n가구수", row: 2, upName: "upChangeRe6", upSequnce: 2 }, + { id: "stepFourSep", title: "분리식\n가구수", row: 2, upName: "upChangeRe6", upSequnce: 3 }, + { id: "etc", title: "비고", row: 0, upName: "upChangeRe7", upSequnce: 1 }, ] -const operationalStatusListOrg : OperationalStatusListProps[] = [ +const operationalStatusListOrg: OperationalStatusListProps[] = [ { - flowRate : '유량\n(㎥/일)', - waterQlty : '유입', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '유량\n(㎥/일)', + waterQlty: '유입', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '유량\n(㎥/일)', - waterQlty : '순수하수', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '유량\n(㎥/일)', + waterQlty: '순수하수', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '유량\n(㎥/일)', - waterQlty : '연계처리수', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '유량\n(㎥/일)', + waterQlty: '연계처리수', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '유량\n(㎥/일)', - waterQlty : '총인설비\n반류수', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '유량\n(㎥/일)', + waterQlty: '총인설비\n반류수', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '유량\n(㎥/일)', - waterQlty : '방류', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '유량\n(㎥/일)', + waterQlty: '방류', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '유입\n(BOD)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '유입\n(BOD)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '방류\n(BOD)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '방류\n(BOD)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '유입\n(T-N)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '유입\n(T-N)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '방류\n(T-N)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '방류\n(T-N)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '유입\n(T-P)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '유입\n(T-P)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '수질\n(mg/L)', - waterQlty : '방류\n(T-P)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '수질\n(mg/L)', + waterQlty: '방류\n(T-P)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '총인\n시설\n수질\n(mg/L)', - waterQlty : '총인설비\n유입(T-P)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '총인\n시설\n수질\n(mg/L)', + waterQlty: '총인설비\n유입(T-P)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, { - flowRate : '총인\n시설\n수질\n(mg/L)', - waterQlty : '총인설비\n방류(T-P)', - yearAvg : '', - yearMax : '', - yearMin : '', - summerAll : '', - summerThree : '', - summerRain : '', - winterAll : '', - winterSunnyAvg : '', - winterSunnyMax : '', - winterSunnyMin : '', - groundWaterAvg : '', - groundWaterSummer : '', - groundWaterWinter : '', - rdi : '', - rdiRain : '', + flowRate: '총인\n시설\n수질\n(mg/L)', + waterQlty: '총인설비\n방류(T-P)', + yearAvg: '', + yearMax: '', + yearMin: '', + summerAll: '', + summerThree: '', + summerRain: '', + winterAll: '', + winterSunnyAvg: '', + winterSunnyMax: '', + winterSunnyMin: '', + groundWaterAvg: '', + groundWaterSummer: '', + groundWaterWinter: '', + rdi: '', + rdiRain: '', }, ] -const chart_inflowRain : lineBarChartDataProps[] = []; -const chart_bodRain : lineBarChartDataProps[] = []; -const chart_inflTp : lineChartDataProps[] = []; -const chart_chongInTp : lineChartDataProps[] = []; -const chart_efflTp : lineChartDataProps[] = []; -const chart_inflTn : lineChartDataProps[] = []; -const chart_efflTn : lineChartDataProps[] = []; -const chart_inflBod : lineChartDataProps[] = []; -const chart_efflBod : lineChartDataProps[] = []; -const waterQltyStandartd : any = []; +const chart_inflowRain: lineBarChartDataProps[] = []; +const chart_bodRain: lineBarChartDataProps[] = []; +const chart_inflTp: lineChartDataProps[] = []; +const chart_chongInTp: lineChartDataProps[] = []; +const chart_efflTp: lineChartDataProps[] = []; +const chart_inflTn: lineChartDataProps[] = []; +const chart_efflTn: lineChartDataProps[] = []; +const chart_inflBod: lineChartDataProps[] = []; +const chart_efflBod: lineChartDataProps[] = []; +const waterQltyStandartd: any = []; -const rainManageList : RainManageListProps[] = []; +const rainManageList: RainManageListProps[] = []; -const drainageEquipmentList : DrainageEquipmentListProps[] = [ +const drainageEquipmentList: DrainageEquipmentListProps[] = [ { - part1 : '청천시청', - part2 : '청천시청', - nowSum : 101, - nowJoin : 102, - nowSep : 103, - stepOneSum : 104, - stepOneJoin : 105, - stepOneSep : 106, - stepTwoSum : 107, - stepTwoJoin : 108, - stepTwoSep : 109, - stepThreeSum : 110, - stepThreeJoin : 111, - stepThreeSep : 112, - stepFourSum : 113, - stepFourJoin : 114, - stepFourSep : 115, - etc : '1050', + part1: '청천시청', + part2: '청천시청', + nowSum: 101, + nowJoin: 102, + nowSep: 103, + stepOneSum: 104, + stepOneJoin: 105, + stepOneSep: 106, + stepTwoSum: 107, + stepTwoJoin: 108, + stepTwoSep: 109, + stepThreeSum: 110, + stepThreeJoin: 111, + stepThreeSep: 112, + stepFourSum: 113, + stepFourJoin: 114, + stepFourSep: 115, + etc: '1050', } ]; +type ApiRes = { data: any; count: any; } /**==================================== * Store - api (데이터 호출) ====================================*/ /* 시설정보 및 운영현황 - 계획 */ -const fetchFacilityPlan = async (targetYear : string | undefined, fcltyCd : string | undefined, set : (fn: (state: DashBoardType) => DashBoardType) => void) => { - try { - const res = await fetchPlanInfo(undefined, undefined, undefined, fcltyCd); - return res; - } catch (error) { - console.error('시설계획정보를 가져오는데 실패했습니다:', error); - set((state) => ({ - ...state, - PlanFclty : { - ...state.PlanFclty, - isLoadingPlanFclty : false, - isLoadingOperationStatus : false - } - })); - return {data: [], count: 0}; - } -}; - -/* 시설정보 및 운영현황 - 운영실태 */ -const fetchOperationStatus = async (searchYear: string|undefined, fcltyCd: string|undefined) => { - try{ - const url = '/dashBoard/mngInfo/' + (searchYear?searchYear:'2023') + '/' + (fcltyCd?fcltyCd:''); - const res = await getFetch('fo', url); - return { data: res.data, count: res.count } - } catch (error) { - console.error('운영실태 데이터를 가져오는데 실패했습니다:', error); +const fetchFacilityPlan = async (targetYear: string | undefined, fcltyCd: string | undefined, set: (fn: (state: DashBoardType) => DashBoardType) => void) => { + try { + const res = await fetchPlanInfo(undefined, undefined, undefined, fcltyCd); + return res; + } catch (error) { + console.error('시설계획정보를 가져오는데 실패했습니다:', error); + set((state) => ({ + ...state, + PlanFclty: { + ...state.PlanFclty, + isLoadingPlanFclty: false, + isLoadingOperationStatus: false + } + })); return { data: [], count: 0 }; } -} +}; + +// 시설들 월별 물사용량 정보 조회 +// const fetchWtrUsgYmList = async (searchYear: string | undefined, fcltCdList: string[]): Promise<WtrUsgYmProps | null> => { +// try { +// const res = await postFetch('fo', '/fclt/flow/wtrUsgYm/' + searchYear, fcltCdList); +// return res; +// } catch (error) { +// console.error('시설들 월별 물사용량 데이터를 가져오는데 실패했습니다:', error); +// return null; +// } +// }; // 주요그래프 데이터 -const fetchMainGraph = async (searchYear: string|undefined, fcltyCd: string|undefined) => { - try { - const url = '/fclt/qlty/hist/' + (searchYear?searchYear:'2023') + '/' + (fcltyCd?fcltyCd:'11680PB001R'); +const fetchMainGraph = async (searchYear: string | undefined, fcltyCd: string | undefined) => { + try { + const url = '/fclt/qlty/hist/' + (searchYear ? searchYear : '2023') + '/' + (fcltyCd ? fcltyCd : '11680PB001R'); const res = await getFetch('fo', url); return { data: res.data, count: res.count } - } catch (error) { + } catch (error) { console.error('주요그래프 데이터를 가져오는데 실패했습니다:', error); return { data: [], count: 0 }; } } // 수질 데이터 청천시, 강우시 재계산 -const calcWaterQltyData = async (data: any[], searchYear : string|undefined, fcltyCd : string|undefined) => { +const calcWaterQltyData = async (data: any[], searchYear: string | undefined, fcltyCd: string | undefined, mainGraphData: any) => { + const operList = { + inflBod: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 유입BOD + inflTN: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 유입TN + inflTP: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 유입TP + efflBod: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 방류BOD + efflTN: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 방류TN + efflTP: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, // 방류TP + tpInflTp: { yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 }, //총인 유입수질 TP(mg/L) + }; const nullToZero = (value: any) => { return value === null || value === undefined || value === '' ? 0 : Number(value); }; const orgData = data; - const res = await fetchMainGraph(searchYear, fcltyCd); - if(res.data.wqltObjs.length> 0) { - const clone = (arr: any[]) => arr?.map(v => ({ ...v })) ?? []; + // const res = await fetchMainGraph(searchYear, fcltyCd); // /fclt/qlty/hist/{year}/{fcltyCd} + const res = mainGraphData; + if (res.data.wqltObjs.length > 0) { + // const clone = (arr: any[]) => arr?.map(v => ({ ...v })) ?? []; const qltyData = res.data.wqltObjs; - const qltyChungchunData = CalcRainEffect(clone(qltyData), 'SUNNY'); - const qltyRainData = CalcRainEffect(clone(qltyData), 'RAIN'); + // const qltyChungchunData = CalcRainEffect(clone(qltyData), 'SUNNY'); + + // 강우시 변수 + const rainEffectDayArray: string[] = []; // (0) 전체변수 - let summerDay = 0; - let noNullSummerDay = 0; - let totalInflBodAvgSummer = 0; - let totalInflTnAvgSummer = 0; - let totalInflTpAvgSummer = 0; - let totalEfflBodAvgSummer = 0; - let totalEfflTnAvgSummer = 0; - let totalEfflTpAvgSummer = 0; - let totalTpInflAvgSummer = 0; + let summerDay = 0, noNullSummerDay = 0; + let totalInflBodAvgSummer = 0, totalInflTnAvgSummer = 0, totalInflTpAvgSummer = 0; + let totalEfflBodAvgSummer = 0, totalEfflTnAvgSummer = 0, totalEfflTpAvgSummer = 0, totalTpInflAvgSummer = 0; - let winterDay = 0; - let noNullWinterDay = 0; - let totalInflBodAvgWinter = 0; - let totalInflTnAvgWinter = 0; - let totalInflTpAvgWinter = 0; - let totalEfflBodAvgWinter = 0; - let totalEfflTnAvgWinter = 0; - let totalEfflTpAvgWinter = 0; - let totalTpInflAvgWinter = 0; - - // 전체 대상 - qltyData.forEach((item: any) => { - const month = item.msrDt.substring(4, 6); // 월 추출 - if(month === '01' || month === '02'){ - winterDay += 1; - if(item.tpInflWqltTp != null && item.tpInflWqltTp != 0){ - noNullWinterDay+=1; - } - totalInflBodAvgWinter += nullToZero(item.inflAssctBfrWqltBod); - totalInflTnAvgWinter += nullToZero(item.inflAssctBfrWqltTn); - totalInflTpAvgWinter += nullToZero(item.inflAssctBfrWqltTp); - totalEfflBodAvgWinter += nullToZero(item.efflWqltBod); - totalEfflTnAvgWinter += nullToZero(item.efflWqltTn); - totalEfflTpAvgWinter += nullToZero(item.efflWqltTp); - totalTpInflAvgWinter += nullToZero(item.tpInflWqltTp); - }else if(month === '06' || month === '07' || month === '08'){ - summerDay += 1; - if(item.tpInflWqltTp != null && item.tpInflWqltTp != 0){ - noNullSummerDay+=1; - } - totalInflBodAvgSummer += nullToZero(item.inflAssctBfrWqltBod); - totalInflTnAvgSummer += nullToZero(item.inflAssctBfrWqltTn); - totalInflTpAvgSummer += nullToZero(item.inflAssctBfrWqltTp); - totalEfflBodAvgSummer += nullToZero(item.efflWqltBod); - totalEfflTnAvgSummer += nullToZero(item.efflWqltTn); - totalEfflTpAvgSummer += nullToZero(item.efflWqltTp); - totalTpInflAvgSummer += nullToZero(item.tpInflWqltTp); - } - }); - - orgData[5].winterAll = qltyDataSetting(totalInflBodAvgWinter, winterDay, 1); // 동절기 유입BOD - orgData[6].winterAll = qltyDataSetting(totalEfflBodAvgWinter, winterDay, 1); // 동절기 방류BOD - orgData[7].winterAll = qltyDataSetting(totalInflTnAvgWinter, winterDay, 3); // 동절기 유입TN - orgData[8].winterAll = qltyDataSetting(totalEfflTnAvgWinter, winterDay, 3); // 동절기 방류TN - orgData[9].winterAll = qltyDataSetting(totalInflTpAvgWinter, winterDay, 3); // 동절기 유입TP - orgData[10].winterAll = qltyDataSetting(totalEfflTpAvgWinter, winterDay, 3); // 동절기 방류TP - - //총인설비 - orgData[11].winterAll = qltyDataSetting(totalTpInflAvgWinter, noNullWinterDay, 3); - orgData[12].winterAll = qltyDataSetting(totalEfflTpAvgWinter, winterDay, 3); - - orgData[5].summerAll = qltyDataSetting(totalInflBodAvgSummer, summerDay, 1); // 하절기 유입BOD - orgData[6].summerAll = qltyDataSetting(totalEfflBodAvgSummer, summerDay, 1); // 하절기 방류BOD - orgData[7].summerAll = qltyDataSetting(totalInflTnAvgSummer, summerDay, 3); // 하절기 유입TN - orgData[8].summerAll = qltyDataSetting(totalEfflTnAvgSummer, summerDay, 3); // 하절기 방류TN - orgData[9].summerAll = qltyDataSetting(totalInflTpAvgSummer, summerDay, 3); // 하절기 유입TP - orgData[10].summerAll = qltyDataSetting(totalEfflTpAvgSummer, summerDay, 3); // 하절기 방류TP - - //총인설비 - orgData[11].summerAll = qltyDataSetting(totalTpInflAvgSummer, noNullSummerDay, 3); - orgData[12].summerAll = qltyDataSetting(totalEfflTpAvgSummer, summerDay, 3); + let winterDay = 0, noNullWinterDay = 0; + let totalInflBodAvgWinter = 0, totalInflTnAvgWinter = 0, totalInflTpAvgWinter = 0; + let totalEfflBodAvgWinter = 0, totalEfflTnAvgWinter = 0, totalEfflTpAvgWinter = 0, totalTpInflAvgWinter = 0; // (1) 청천시 변수 // (1-1) 날짜 count - let chungchunSummerDay = 0; - let chungchunWinterDay = 0; - let noNullChungchunSummerDay = 0; - let noNullChungchunWinterDay = 0; + + let chungchunSummerDay = 0, noNullChungchunSummerDay = 0; + let chungchunWinterDay = 0, noNullChungchunWinterDay = 0; // (1-2) 하절기 - let chungchunInflBodAvgSummer = 0; // 하절기 청천시 유입BOD - let chungchunInflTnAvgSummer = 0; // 하절기 청천시 유입TN - let chungchunInflTpAvgSummer = 0; // 하절기 청천시 유입TP - let chungchunEfflBodAvgSummer = 0; // 하절기 청천시 방류BOD - let chungchunEfflTnAvgSummer = 0; // 하절기 청천시 방류TN - let chungchunEfflTpAvgSummer = 0; // 하절기 청천시 방류TP - let chungchunTpInflAvgSummer = 0; // 하절기 청천시 총인설비 유입TP + let chungchunInflBodAvgSummer = 0, chungchunInflTnAvgSummer = 0, chungchunInflTpAvgSummer = 0; + let chungchunEfflBodAvgSummer = 0, chungchunEfflTnAvgSummer = 0, chungchunEfflTpAvgSummer = 0; + let chungchunTpInflAvgSummer = 0; // 하절기 청천시 총인설비 유입TP // (1-3) 동절기 (최대, 최소, 평균 다 필요) - let chungchunInflBodAvg = 0; - let chungchunInflTnAvg = 0; - let chungchunInflTpAvg = 0; - let chungchunTpInflAvg = 0; - let chungchunInflBodMax = 0; - let chungchunInflTnMax = 0; - let chungchunInflTpMax = 0; - let chungchunTpInflMax = 0; + let chungchunInflBodAvg = 0, chungchunInflTnAvg = 0, chungchunInflTpAvg = 0, chungchunTpInflAvg = 0; + let chungchunInflBodMax = 0, chungchunInflTnMax = 0, chungchunInflTpMax = 0, chungchunTpInflMax = 0; let chungchunInflBodMin = 99999999; - let chungchunInflTnMin = 99999999; - let chungchunInflTpMin = 99999999; - let chungchunTpInflMin = 99999999; + let chungchunInflTnMin = 99999999; + let chungchunInflTpMin = 99999999; + let chungchunTpInflMin = 99999999; - let chungchunEfflBodAvg = 0; - let chungchunEfflTnAvg = 0; - let chungchunEfflTpAvg = 0; - let chungchunEfflBodMax = 0; - let chungchunEfflTnMax = 0; - let chungchunEfflTpMax = 0; + let chungchunEfflBodAvg = 0, chungchunEfflTnAvg = 0, chungchunEfflTpAvg = 0; + let chungchunEfflBodMax = 0, chungchunEfflTnMax = 0, chungchunEfflTpMax = 0; let chungchunEfflBodMin = 99999999; - let chungchunEfflTnMin = 99999999; - let chungchunEfflTpMin = 99999999; - - // 청천시 - qltyChungchunData.forEach((item: any) => { - const month = item.msrDt.substring(4, 6); // 월 추출 - if(month === '01' || month === '02'){ - chungchunWinterDay += 1; - if(item.tpInflWqltTp != null && item.tpInflWqltTp != 0){ - noNullChungchunWinterDay+=1; - } - - const inflbod = nullToZero(item.inflAssctBfrWqltBod); - const infltn = nullToZero(item.inflAssctBfrWqltTn); - const infltp = nullToZero(item.inflAssctBfrWqltTp); - const effbod = nullToZero(item.efflWqltBod); - const efftn = nullToZero(item.efflWqltTn); - const efftp = nullToZero(item.efflWqltTp); - const tpInfl = nullToZero(item.tpInflWqltTp); - - chungchunInflBodAvg += inflbod; - chungchunInflTnAvg += infltn; - chungchunInflTpAvg += infltp; - chungchunEfflBodAvg += effbod; - chungchunEfflTnAvg += efftn; - chungchunEfflTpAvg += efftp; - chungchunTpInflAvg += tpInfl; - - if(chungchunInflBodMax < inflbod) chungchunInflBodMax = inflbod; - if(chungchunInflTnMax < infltn) chungchunInflTnMax = infltn; - if(chungchunInflTpMax < infltp) chungchunInflTpMax = infltp; - if(chungchunEfflBodMax < effbod) chungchunEfflBodMax = effbod; - if(chungchunEfflTnMax < efftn) chungchunEfflTnMax = efftn; - if(chungchunEfflTpMax < efftp) chungchunEfflTpMax = efftp; - if(chungchunTpInflMax < tpInfl) chungchunTpInflMax = tpInfl; - - if(chungchunInflBodMin > inflbod) chungchunInflBodMin = inflbod; - if(chungchunInflTnMin > infltn) chungchunInflTnMin = infltn; - if(chungchunInflTpMin > infltp) chungchunInflTpMin = infltp; - if(chungchunEfflBodMin > effbod) chungchunEfflBodMin = effbod; - if(chungchunEfflTnMin > efftn) chungchunEfflTnMin = efftn; - if(chungchunEfflTpMin > efftp) chungchunEfflTpMin = efftp; - if(chungchunTpInflMin > tpInfl) chungchunTpInflMin = tpInfl; - }else if(month === '06' || month === '07' || month === '08'){ - chungchunSummerDay += 1; - if(item.tpInflWqltTp != null && item.tpInflWqltTp != 0){ - noNullChungchunSummerDay+=1; - } - - chungchunInflBodAvgSummer += nullToZero(item.inflAssctBfrWqltBod); - chungchunInflTnAvgSummer += nullToZero(item.inflAssctBfrWqltTn); - chungchunInflTpAvgSummer += nullToZero(item.inflAssctBfrWqltTp); - chungchunEfflBodAvgSummer += nullToZero(item.efflWqltBod); - chungchunEfflTnAvgSummer += nullToZero(item.efflWqltTn); - chungchunEfflTpAvgSummer += nullToZero(item.efflWqltTp); - chungchunTpInflAvgSummer += nullToZero(item.tpInflWqltTp); - } - }); - - // 청천시 수질 데이터 수정 - // 하절기 - orgData[5].summerRain = qltyDataSetting(chungchunInflBodAvgSummer, chungchunSummerDay, 1); - orgData[6].summerRain = qltyDataSetting(chungchunEfflBodAvgSummer, chungchunSummerDay, 1); - orgData[7].summerRain = qltyDataSetting(chungchunInflTnAvgSummer, chungchunSummerDay, 3); - orgData[8].summerRain = qltyDataSetting(chungchunEfflTnAvgSummer, chungchunSummerDay, 3); - orgData[9].summerRain = qltyDataSetting(chungchunInflTpAvgSummer, chungchunSummerDay, 3); - orgData[10].summerRain = qltyDataSetting(chungchunEfflTpAvgSummer, chungchunSummerDay, 3); - - // 총인설비 - orgData[11].summerRain = qltyDataSetting(chungchunTpInflAvgSummer, noNullChungchunSummerDay, 3); - orgData[12].summerRain = qltyDataSetting(chungchunEfflTpAvgSummer, chungchunSummerDay, 3); - - // 동절기 - orgData[5].winterSunnyAvg = qltyDataSetting(chungchunInflBodAvg, chungchunWinterDay, 1); - orgData[6].winterSunnyAvg = qltyDataSetting(chungchunEfflBodAvg, chungchunWinterDay, 1); - orgData[7].winterSunnyAvg = qltyDataSetting(chungchunInflTnAvg, chungchunWinterDay, 3); - orgData[8].winterSunnyAvg = qltyDataSetting(chungchunEfflTnAvg, chungchunWinterDay, 3); - orgData[9].winterSunnyAvg = qltyDataSetting(chungchunInflTpAvg, chungchunWinterDay, 3); - orgData[10].winterSunnyAvg = qltyDataSetting(chungchunEfflTpAvg, chungchunWinterDay, 3); - - // 총인설비 - orgData[11].winterSunnyAvg = qltyDataSetting(chungchunTpInflAvg, noNullChungchunWinterDay, 3); - orgData[12].winterSunnyAvg = qltyDataSetting(chungchunInflTpAvg, chungchunWinterDay, 3); - - orgData[5].winterSunnyMax = roundUp(chungchunInflBodMax, 1).toFixed(1); - orgData[6].winterSunnyMax = roundUp(chungchunEfflBodMax, 1).toFixed(1); - orgData[7].winterSunnyMax = roundUp(chungchunInflTnMax, 3).toFixed(3); - orgData[8].winterSunnyMax = roundUp(chungchunEfflTnMax, 3).toFixed(3); - orgData[9].winterSunnyMax = roundUp(chungchunInflTpMax, 3).toFixed(3); - orgData[10].winterSunnyMax = roundUp(chungchunEfflTpMax, 3).toFixed(3); - orgData[11].winterSunnyMax = roundUp(chungchunTpInflMax, 3).toFixed(3); - orgData[12].winterSunnyMax = roundUp(chungchunInflTpMax, 3).toFixed(3); - - orgData[5].winterSunnyMin = chungchunInflBodMin === 99999999 ? '0.0' : roundUp(chungchunInflBodMin, 1).toFixed(1); - orgData[6].winterSunnyMin = chungchunEfflBodMin === 99999999 ? '0.0' : roundUp(chungchunEfflBodMin, 1).toFixed(1); - orgData[7].winterSunnyMin = chungchunInflTnMin === 99999999 ? '0.000' : roundUp(chungchunInflTnMin, 3).toFixed(3); - orgData[8].winterSunnyMin = chungchunEfflTnMin === 99999999 ? '0.000' : roundUp(chungchunEfflTnMin, 3).toFixed(3); - orgData[9].winterSunnyMin = chungchunInflTpMin === 99999999 ? '0.000' : roundUp(chungchunInflTpMin, 3).toFixed(3); - orgData[10].winterSunnyMin = chungchunEfflTpMin === 99999999 ? '0.000' : roundUp(chungchunEfflTpMin, 3).toFixed(3); - orgData[11].winterSunnyMin = chungchunTpInflMin === 99999999 ? '0.000' : roundUp(chungchunTpInflMin, 3).toFixed(3); - orgData[12].winterSunnyMin = chungchunInflTpMin === 99999999 ? '0.000' : roundUp(chungchunInflTpMin, 3).toFixed(3); + let chungchunEfflTnMin = 99999999; + let chungchunEfflTpMin = 99999999; // 강우시 변수 - let rainSummerDay = 0; - let noNullRainSummerDay = 0; + let rainSummerDay = 0, noNullRainSummerDay = 0; + let rainInflBodAvg = 0, rainInflTnAvg = 0, rainInflTpAvg = 0; + let rainEfflBodAvg = 0, rainEfflTnAvg = 0, rainEfflTpAvg = 0, rainTpInflAvg = 0; - let rainInflBodAvg = 0; - let rainInflTnAvg = 0; - let rainInflTpAvg = 0; - let rainEfflBodAvg = 0; - let rainEfflTnAvg = 0; - let rainEfflTpAvg = 0; - let rainTpInflAvg = 0; - - // 강우시 - qltyRainData.forEach((item: any) => { + let rainEffectDay = 0; + // 전체 대상 + const totalDay = qltyData.length; + let noNullYearDay = 0; + qltyData.forEach((item: any) => { + if (item.infoRnDay == null) item.infoRnDay = 0; const month = item.msrDt.substring(4, 6); // 월 추출 - if(month === '06' || month === '07' || month === '08'){ - rainSummerDay += 1; - if(item.tpInflWqltTp != null && item.tpInflWqltTp != 0){ - noNullRainSummerDay+=1; - } - - rainInflBodAvg += nullToZero(item.inflAssctBfrWqltBod); - rainInflTnAvg += nullToZero(item.inflAssctBfrWqltTn); - rainInflTpAvg += nullToZero(item.inflAssctBfrWqltTp); - rainEfflBodAvg += nullToZero(item.efflWqltBod); - rainEfflTnAvg += nullToZero(item.efflWqltTn); - rainEfflTpAvg += nullToZero(item.efflWqltTp); - rainTpInflAvg += nullToZero(item.tpInflWqltTp); + + if (item.infoRnDay >= 3) { + rainEffectDay = 2; + } else { + rainEffectDay--; } + + if (rainEffectDay >= 0) { + rainEffectDayArray.push(item.msrDt); + // 강우영향일 + if (month === '06' || month === '07' || month === '08') { + rainSummerDay += 1; + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullRainSummerDay += 1; + } + + rainInflBodAvg += nullToZero(item.inflAssctBfrWqltBod); + rainInflTnAvg += nullToZero(item.inflAssctBfrWqltTn); + rainInflTpAvg += nullToZero(item.inflAssctBfrWqltTp); + rainEfflBodAvg += nullToZero(item.efflWqltBod); + rainEfflTnAvg += nullToZero(item.efflWqltTn); + rainEfflTpAvg += nullToZero(item.efflWqltTp); + rainTpInflAvg += nullToZero(item.tpInflWqltTp); + } + } else if (rainEffectDay < 0) { //청천시 + + if (month === '01' || month === '02') { + chungchunWinterDay += 1; + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullChungchunWinterDay += 1; + } + + const inflbod = nullToZero(item.inflAssctBfrWqltBod); + const infltn = nullToZero(item.inflAssctBfrWqltTn); + const infltp = nullToZero(item.inflAssctBfrWqltTp); + const effbod = nullToZero(item.efflWqltBod); + const efftn = nullToZero(item.efflWqltTn); + const efftp = nullToZero(item.efflWqltTp); + const tpInfl = nullToZero(item.tpInflWqltTp); + + chungchunInflBodAvg += inflbod; + chungchunInflTnAvg += infltn; + chungchunInflTpAvg += infltp; + chungchunEfflBodAvg += effbod; + chungchunEfflTnAvg += efftn; + chungchunEfflTpAvg += efftp; + chungchunTpInflAvg += tpInfl; + + if (chungchunInflBodMax < inflbod) chungchunInflBodMax = inflbod; + if (chungchunInflTnMax < infltn) chungchunInflTnMax = infltn; + if (chungchunInflTpMax < infltp) chungchunInflTpMax = infltp; + if (chungchunEfflBodMax < effbod) chungchunEfflBodMax = effbod; + if (chungchunEfflTnMax < efftn) chungchunEfflTnMax = efftn; + if (chungchunEfflTpMax < efftp) chungchunEfflTpMax = efftp; + if (chungchunTpInflMax < tpInfl) chungchunTpInflMax = tpInfl; + + if (chungchunInflBodMin > inflbod) chungchunInflBodMin = inflbod; + if (chungchunInflTnMin > infltn) chungchunInflTnMin = infltn; + if (chungchunInflTpMin > infltp) chungchunInflTpMin = infltp; + if (chungchunEfflBodMin > effbod) chungchunEfflBodMin = effbod; + if (chungchunEfflTnMin > efftn) chungchunEfflTnMin = efftn; + if (chungchunEfflTpMin > efftp) chungchunEfflTpMin = efftp; + if (chungchunTpInflMin > tpInfl) chungchunTpInflMin = tpInfl; + } else if (month === '06' || month === '07' || month === '08') { + chungchunSummerDay += 1; + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullChungchunSummerDay += 1; + } + + chungchunInflBodAvgSummer += nullToZero(item.inflAssctBfrWqltBod); + chungchunInflTnAvgSummer += nullToZero(item.inflAssctBfrWqltTn); + chungchunInflTpAvgSummer += nullToZero(item.inflAssctBfrWqltTp); + chungchunEfflBodAvgSummer += nullToZero(item.efflWqltBod); + chungchunEfflTnAvgSummer += nullToZero(item.efflWqltTn); + chungchunEfflTpAvgSummer += nullToZero(item.efflWqltTp); + chungchunTpInflAvgSummer += nullToZero(item.tpInflWqltTp); + } + } + + + if (month === '01' || month === '02') { + winterDay += 1; + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullWinterDay += 1; + } + totalInflBodAvgWinter += nullToZero(item.inflAssctBfrWqltBod); + totalInflTnAvgWinter += nullToZero(item.inflAssctBfrWqltTn); + totalInflTpAvgWinter += nullToZero(item.inflAssctBfrWqltTp); + totalEfflBodAvgWinter += nullToZero(item.efflWqltBod); + totalEfflTnAvgWinter += nullToZero(item.efflWqltTn); + totalEfflTpAvgWinter += nullToZero(item.efflWqltTp); + totalTpInflAvgWinter += nullToZero(item.tpInflWqltTp); //총인 유입수질 TP(mg/L) + } else if (month === '06' || month === '07' || month === '08') { + summerDay += 1; + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullSummerDay += 1; + } + totalInflBodAvgSummer += nullToZero(item.inflAssctBfrWqltBod); + totalInflTnAvgSummer += nullToZero(item.inflAssctBfrWqltTn); + totalInflTpAvgSummer += nullToZero(item.inflAssctBfrWqltTp); + totalEfflBodAvgSummer += nullToZero(item.efflWqltBod); + totalEfflTnAvgSummer += nullToZero(item.efflWqltTn); + totalEfflTpAvgSummer += nullToZero(item.efflWqltTp); + totalTpInflAvgSummer += nullToZero(item.tpInflWqltTp); + } + + if (item.tpInflWqltTp != null && item.tpInflWqltTp != 0) { + noNullYearDay += 1; + } + operList.inflBod.yearTot += nullToZero(item.inflAssctBfrWqltBod); + operList.inflTN.yearTot += nullToZero(item.inflAssctBfrWqltTn); + operList.inflTP.yearTot += nullToZero(item.inflAssctBfrWqltTp); + operList.efflBod.yearTot += nullToZero(item.efflWqltBod); + operList.efflTN.yearTot += nullToZero(item.efflWqltTn); + operList.efflTP.yearTot += nullToZero(item.efflWqltTp); + operList.tpInflTp.yearTot += nullToZero(item.tpInflWqltTp); + // 최대 최소 + operList.inflBod.yearMax = Math.max(operList.inflBod.yearMax, nullToZero(item.inflAssctBfrWqltBod)); + operList.inflBod.yearMin = Math.min(operList.inflBod.yearMin, nullToZero(item.inflAssctBfrWqltBod)); + operList.inflTN.yearMax = Math.max(operList.inflTN.yearMax, nullToZero(item.inflAssctBfrWqltTn)); + operList.inflTN.yearMin = Math.min(operList.inflTN.yearMin, nullToZero(item.inflAssctBfrWqltTn)); + operList.inflTP.yearMax = Math.max(operList.inflTP.yearMax, nullToZero(item.inflAssctBfrWqltTp)); + operList.inflTP.yearMin = Math.min(operList.inflTP.yearMin, nullToZero(item.inflAssctBfrWqltTp)); + operList.efflBod.yearMax = Math.max(operList.efflBod.yearMax, nullToZero(item.efflWqltBod)); + operList.efflBod.yearMin = Math.min(operList.efflBod.yearMin, nullToZero(item.efflWqltBod)); + operList.efflTN.yearMax = Math.max(operList.efflTN.yearMax, nullToZero(item.efflWqltTn)); + operList.efflTN.yearMin = Math.min(operList.efflTN.yearMin, nullToZero(item.efflWqltTn)); + operList.efflTP.yearMax = Math.max(operList.efflTP.yearMax, nullToZero(item.efflWqltTp)); + operList.efflTP.yearMin = Math.min(operList.efflTP.yearMin, nullToZero(item.efflWqltTp)); + operList.tpInflTp.yearMax = Math.max(operList.tpInflTp.yearMax, nullToZero(item.tpInflWqltTp)); + operList.tpInflTp.yearMin = Math.min(operList.tpInflTp.yearMin, nullToZero(item.tpInflWqltTp)); + }); + operList.inflBod.yearAvg = operList.inflBod.yearTot / totalDay; + operList.inflTN.yearAvg = operList.inflTN.yearTot / totalDay; + operList.inflTP.yearAvg = operList.inflTP.yearTot / totalDay; + operList.efflBod.yearAvg = operList.efflBod.yearTot / totalDay; + operList.efflTN.yearAvg = operList.efflTN.yearTot / totalDay; + operList.efflTP.yearAvg = operList.efflTP.yearTot / totalDay; + operList.tpInflTp.yearAvg = operList.tpInflTp.yearTot / totalDay; + + orgData[5].yearAvg = qltyDataSetting(operList.inflBod.yearTot, totalDay, 1); // 연간 유입BOD + orgData[6].yearAvg = qltyDataSetting(operList.efflBod.yearTot, totalDay, 1); // 연간 방류BOD + orgData[7].yearAvg = qltyDataSetting(operList.inflTN.yearTot, totalDay, 3); // 연간 유입TN + orgData[8].yearAvg = qltyDataSetting(operList.efflTN.yearTot, totalDay, 3); // 연간 방류TN + orgData[9].yearAvg = qltyDataSetting(operList.inflTP.yearTot, totalDay, 3); // 연간 유입TP + orgData[10].yearAvg = qltyDataSetting(operList.efflTP.yearTot, totalDay, 3); // 연간 방류TP + //총인설비 + orgData[11].yearAvg = qltyDataSetting(operList.tpInflTp.yearTot, noNullYearDay, 3); // 연간 총인 유입수질 TP + orgData[12].yearAvg = qltyDataSetting(operList.efflTP.yearTot, totalDay, 3); // 연간 총인 유입수질 TP + + orgData[5].yearMax = roundUp(operList.inflBod.yearMax, 1); // 연간 유입BOD + orgData[6].yearMax = roundUp(operList.efflBod.yearMax, 1); // 연간 방류BOD + orgData[7].yearMax = roundUp(operList.inflTN.yearMax, 3).toFixed(3); // 연간 유입TN + orgData[8].yearMax = roundUp(operList.efflTN.yearMax, 3).toFixed(3); // 연간 방류TN + orgData[9].yearMax = roundUp(operList.inflTP.yearMax, 3).toFixed(3); // 연간 유입TP + orgData[10].yearMax = roundUp(operList.efflTP.yearMax, 3).toFixed(3); // 연간 방류TP + orgData[11].yearMax = roundUp(operList.tpInflTp.yearMax, 3).toFixed(3); // 연간 총인 유입수질 TP + orgData[12].yearMax = roundUp(operList.efflTP.yearMax, 3).toFixed(3); // 연간 총인 유입수질 TP + + orgData[5].yearMin = roundUp(operList.inflBod.yearMin, 1); // 연간 유입BOD + orgData[6].yearMin = roundUp(operList.efflBod.yearMin, 1); // 연간 방류BOD + orgData[7].yearMin = roundUp(operList.inflTN.yearMin, 3).toFixed(3); // 연간 유입TN + orgData[8].yearMin = roundUp(operList.efflTN.yearMin, 3).toFixed(3); // 연간 방류TN + orgData[9].yearMin = roundUp(operList.inflTP.yearMin, 3).toFixed(3); // 연간 유입TP + orgData[10].yearMin = roundUp(operList.efflTP.yearMin, 3).toFixed(3); // 연간 방류TP + orgData[11].yearMin = roundUp(operList.tpInflTp.yearMin, 3).toFixed(3); // 연간 총인 유입수질 TP + orgData[12].yearMin = roundUp(operList.efflTP.yearMin, 3).toFixed(3); // 연간 총인 유입수질 TP + + orgData[5].summerAll = qltyDataSetting(totalInflBodAvgSummer, summerDay, 1); // 하절기 전기간 유입BOD + orgData[6].summerAll = qltyDataSetting(totalEfflBodAvgSummer, summerDay, 1); // 하절기 전기간 방류BOD + orgData[7].summerAll = qltyDataSetting(totalInflTnAvgSummer, summerDay, 3); // 하절기 전기간 유입TN + orgData[8].summerAll = qltyDataSetting(totalEfflTnAvgSummer, summerDay, 3); // 하절기 전기간 방류TN + orgData[9].summerAll = qltyDataSetting(totalInflTpAvgSummer, summerDay, 3); // 하절기 전기간 유입TP + orgData[10].summerAll = qltyDataSetting(totalEfflTpAvgSummer, summerDay, 3); // 하절기 전기간 방류TP + //총인설비 + orgData[11].summerAll = qltyDataSetting(totalTpInflAvgSummer, noNullSummerDay, 3); + orgData[12].summerAll = qltyDataSetting(totalEfflTpAvgSummer, summerDay, 3); + // 강우시 수질 데이터 수정 orgData[5].summerThree = qltyDataSetting(rainInflBodAvg, rainSummerDay, 1); orgData[6].summerThree = qltyDataSetting(rainEfflBodAvg, rainSummerDay, 1); @@ -1010,10 +1011,61 @@ const calcWaterQltyData = async (data: any[], searchYear : string|undefined, fcl orgData[8].summerThree = qltyDataSetting(rainEfflTnAvg, rainSummerDay, 3); orgData[9].summerThree = qltyDataSetting(rainInflTpAvg, rainSummerDay, 3); orgData[10].summerThree = qltyDataSetting(rainEfflTpAvg, rainSummerDay, 3); - // 총인설비 orgData[11].summerThree = qltyDataSetting(rainTpInflAvg, noNullRainSummerDay, 3); - orgData[12].summerThree = qltyDataSetting(rainInflTpAvg, rainSummerDay, 3); + orgData[12].summerThree = qltyDataSetting(rainEfflTpAvg, rainSummerDay, 3); + + // 하절기 청천시 수질 데이터 + orgData[5].summerRain = qltyDataSetting(chungchunInflBodAvgSummer, chungchunSummerDay, 1); + orgData[6].summerRain = qltyDataSetting(chungchunEfflBodAvgSummer, chungchunSummerDay, 1); + orgData[7].summerRain = qltyDataSetting(chungchunInflTnAvgSummer, chungchunSummerDay, 3); + orgData[8].summerRain = qltyDataSetting(chungchunEfflTnAvgSummer, chungchunSummerDay, 3); + orgData[9].summerRain = qltyDataSetting(chungchunInflTpAvgSummer, chungchunSummerDay, 3); + orgData[10].summerRain = qltyDataSetting(chungchunEfflTpAvgSummer, chungchunSummerDay, 3); + // 총인설비 + orgData[11].summerRain = qltyDataSetting(chungchunTpInflAvgSummer, noNullChungchunSummerDay, 3); + orgData[12].summerRain = qltyDataSetting(chungchunEfflTpAvgSummer, chungchunSummerDay, 3); + + orgData[5].winterAll = qltyDataSetting(totalInflBodAvgWinter, winterDay, 1); // 동절기 전기간 유입BOD + orgData[6].winterAll = qltyDataSetting(totalEfflBodAvgWinter, winterDay, 1); // 동절기 전기간 방류BOD + orgData[7].winterAll = qltyDataSetting(totalInflTnAvgWinter, winterDay, 3); // 동절기 전기간 유입TN + orgData[8].winterAll = qltyDataSetting(totalEfflTnAvgWinter, winterDay, 3); // 동절기 전기간 방류TN + orgData[9].winterAll = qltyDataSetting(totalInflTpAvgWinter, winterDay, 3); // 동절기 전기간 유입TP + orgData[10].winterAll = qltyDataSetting(totalEfflTpAvgWinter, winterDay, 3); // 동절기 전기간 방류TP + //총인설비 + orgData[11].winterAll = qltyDataSetting(totalTpInflAvgWinter, noNullWinterDay, 3); + orgData[12].winterAll = qltyDataSetting(totalEfflTpAvgWinter, winterDay, 3); + + // 동절기 청천시 수질 데이터 + orgData[5].winterSunnyAvg = qltyDataSetting(chungchunInflBodAvg, chungchunWinterDay, 1); + orgData[6].winterSunnyAvg = qltyDataSetting(chungchunEfflBodAvg, chungchunWinterDay, 1); + orgData[7].winterSunnyAvg = qltyDataSetting(chungchunInflTnAvg, chungchunWinterDay, 3); + orgData[8].winterSunnyAvg = qltyDataSetting(chungchunEfflTnAvg, chungchunWinterDay, 3); + orgData[9].winterSunnyAvg = qltyDataSetting(chungchunInflTpAvg, chungchunWinterDay, 3); + orgData[10].winterSunnyAvg = qltyDataSetting(chungchunEfflTpAvg, chungchunWinterDay, 3); + // 총인설비 + orgData[11].winterSunnyAvg = qltyDataSetting(chungchunTpInflAvg, noNullChungchunWinterDay, 3); + orgData[12].winterSunnyAvg = qltyDataSetting(chungchunEfflTpAvg, chungchunWinterDay, 3); + + orgData[5].winterSunnyMax = roundUp(chungchunInflBodMax, 1).toFixed(1); + orgData[6].winterSunnyMax = roundUp(chungchunEfflBodMax, 1).toFixed(1); + orgData[7].winterSunnyMax = roundUp(chungchunInflTnMax, 3).toFixed(3); + orgData[8].winterSunnyMax = roundUp(chungchunEfflTnMax, 3).toFixed(3); + orgData[9].winterSunnyMax = roundUp(chungchunInflTpMax, 3).toFixed(3); + orgData[10].winterSunnyMax = roundUp(chungchunEfflTpMax, 3).toFixed(3); + orgData[11].winterSunnyMax = roundUp(chungchunTpInflMax, 3).toFixed(3); + orgData[12].winterSunnyMax = roundUp(chungchunEfflTpMax, 3).toFixed(3); + + orgData[5].winterSunnyMin = chungchunInflBodMin === 99999999 ? '0.0' : roundUp(chungchunInflBodMin, 1).toFixed(1); + orgData[6].winterSunnyMin = chungchunEfflBodMin === 99999999 ? '0.0' : roundUp(chungchunEfflBodMin, 1).toFixed(1); + orgData[7].winterSunnyMin = chungchunInflTnMin === 99999999 ? '0.000' : roundUp(chungchunInflTnMin, 3).toFixed(3); + orgData[8].winterSunnyMin = chungchunEfflTnMin === 99999999 ? '0.000' : roundUp(chungchunEfflTnMin, 3).toFixed(3); + orgData[9].winterSunnyMin = chungchunInflTpMin === 99999999 ? '0.000' : roundUp(chungchunInflTpMin, 3).toFixed(3); + orgData[10].winterSunnyMin = chungchunEfflTpMin === 99999999 ? '0.000' : roundUp(chungchunEfflTpMin, 3).toFixed(3); + orgData[11].winterSunnyMin = chungchunTpInflMin === 99999999 ? '0.000' : roundUp(chungchunTpInflMin, 3).toFixed(3); + orgData[12].winterSunnyMin = chungchunEfflTpMin === 99999999 ? '0.000' : roundUp(chungchunEfflTpMin, 3).toFixed(3); + + return orgData; } else { @@ -1039,256 +1091,257 @@ export type DashBoardActions = { export type DashBoardType = { DashboardMain: { isInit: boolean; - topLabelArray:string[]; + topLabelArray: string[]; }, - FcltyMain : { - isInit : boolean; - upHeadListFclty : TableUpperProps[]; - fcltyList : mainFcltyProps[]; + FcltyMain: { + isInit: boolean; + upHeadListFclty: TableUpperProps[]; + fcltyList: mainFcltyProps[]; }, - PlanFclty : { - isInit : boolean; - planYearList : string[]; - upHeadListPlanFclty : TableUpperProps[]; - planFcltyList : planFcltyListProps[]; - waterUsage1 : number | '', - waterUsage2 : number | '', - waterUsage3 : number | '', - isLoadingPlanFclty : boolean; + PlanFclty: { + isInit: boolean; + planYearList: string[]; + upHeadListPlanFclty: TableUpperProps[]; + planFcltyList: planFcltyListProps[]; + waterUsage1: number | '', + waterUsage2: number | '', + waterUsage3: number | '', + isLoadingPlanFclty: boolean; }, - OperationStatus : { - isInit : boolean; - upHeadListOperationStatus : TableUpperProps[]; - operationalStatusList : any; - isLoadingOperationStatus : boolean; + OperationStatus: { + isInit: boolean; + upHeadListOperationStatus: TableUpperProps[]; + operationalStatusList: any; + isLoadingOperationStatus: boolean; } - MainGraph : { - isInit : boolean; - chart_inflowRain : lineBarChartDataProps[]; - chart_bodRain : lineBarChartDataProps[]; - chart_inflTp : lineChartDataProps[]; - chart_chongInTp : lineChartDataProps[]; - chart_efflTp : lineChartDataProps[]; - chart_inflTn : lineChartDataProps[]; - chart_efflTn : lineChartDataProps[]; - chart_inflBod : lineChartDataProps[]; - chart_efflBod : lineChartDataProps[]; - waterQltyStandartd : any; - isLoadingMainGraph : boolean; + MainGraph: { + isInit: boolean; + chart_inflowRain: lineBarChartDataProps[]; + chart_bodRain: lineBarChartDataProps[]; + chart_inflTp: lineChartDataProps[]; + chart_chongInTp: lineChartDataProps[]; + chart_efflTp: lineChartDataProps[]; + chart_inflTn: lineChartDataProps[]; + chart_efflTn: lineChartDataProps[]; + chart_inflBod: lineChartDataProps[]; + chart_efflBod: lineChartDataProps[]; + waterQltyStandartd: any; + isLoadingMainGraph: boolean; }, - RainManage : { - isInit : boolean; - upHeadListRainManage : TableUpperProps[]; - rainManageList : RainManageListProps[]; - excelList : RainManageExcelType[]; - isLoadingRainManage : boolean; + RainManage: { + isInit: boolean; + upHeadListRainManage: TableUpperProps[]; + rainManageList: RainManageListProps[]; + excelList: RainManageExcelType[]; + isLoadingRainManage: boolean; + mainGraphData: any; + mainGraphYear: string; + mainGraphFcltyCd: string; + flowRateData: any; + flowRateYear: string; + flowRateFcltyCd: string; }, - - DrainageEquipment : { - isInit : boolean; - upHeadListDrainageEquipment : TableUpperProps[]; - drainageEquipmentList : DrainageEquipmentListProps[]; - drainageEquipmentExcelList : DrainageEquipmentExcelType[]; + + DrainageEquipment: { + isInit: boolean; + upHeadListDrainageEquipment: TableUpperProps[]; + drainageEquipmentList: DrainageEquipmentListProps[]; + drainageEquipmentExcelList: DrainageEquipmentExcelType[]; } } export type DashBoardStore = DashBoardType & DashBoardActions; -export const dashBoardInitState:DashBoardType = { +export const dashBoardInitState: DashBoardType = { DashboardMain: { isInit: false, topLabelArray: DashBoardTopLabelArray, }, - - FcltyMain : { - isInit : false, - upHeadListFclty : upHeadListFclty, - fcltyList : fcltyList, + + FcltyMain: { + isInit: false, + upHeadListFclty: upHeadListFclty, + fcltyList: fcltyList, }, - PlanFclty : { - isInit : true, - planYearList : planYearList, - upHeadListPlanFclty : upHeadListPlanFclty, - planFcltyList : planFcltyList, - waterUsage1 : 0, - waterUsage2 : 0, - waterUsage3 : 0, - isLoadingPlanFclty : false, + PlanFclty: { + isInit: true, + planYearList: planYearList, + upHeadListPlanFclty: upHeadListPlanFclty, + planFcltyList: planFcltyList, + waterUsage1: 0, + waterUsage2: 0, + waterUsage3: 0, + isLoadingPlanFclty: false, }, - OperationStatus : { - isInit : false, - upHeadListOperationStatus : upHeadListOperationStatus, - operationalStatusList : operationalStatusListOrg, - isLoadingOperationStatus : false, + OperationStatus: { + isInit: false, + upHeadListOperationStatus: upHeadListOperationStatus, + operationalStatusList: operationalStatusListOrg, + isLoadingOperationStatus: false, }, - MainGraph : { - isInit : false, - chart_inflowRain : chart_inflowRain, - chart_bodRain : chart_bodRain, - chart_inflTp : chart_inflTp, - chart_chongInTp : chart_chongInTp, - chart_efflTp : chart_efflTp, - chart_inflTn : chart_inflTn, - chart_efflTn : chart_efflTn, - chart_inflBod : chart_inflBod, - chart_efflBod : chart_efflBod, - waterQltyStandartd : waterQltyStandartd, - isLoadingMainGraph : false, + MainGraph: { + isInit: false, + chart_inflowRain: chart_inflowRain, + chart_bodRain: chart_bodRain, + chart_inflTp: chart_inflTp, + chart_chongInTp: chart_chongInTp, + chart_efflTp: chart_efflTp, + chart_inflTn: chart_inflTn, + chart_efflTn: chart_efflTn, + chart_inflBod: chart_inflBod, + chart_efflBod: chart_efflBod, + waterQltyStandartd: waterQltyStandartd, + isLoadingMainGraph: false, }, - RainManage : { - isInit : false, - upHeadListRainManage : upHeadListRainManage, - rainManageList : rainManageList, - excelList : [], - isLoadingRainManage : false, + RainManage: { + isInit: false, + upHeadListRainManage: upHeadListRainManage, + rainManageList: rainManageList, + excelList: [], + isLoadingRainManage: false, + mainGraphData: null, + mainGraphYear: '', + mainGraphFcltyCd: '', + flowRateData: null, + flowRateYear: '', + flowRateFcltyCd: '', }, - DrainageEquipment : { - isInit : false, - upHeadListDrainageEquipment : upHeadListDrainageEquipment, - drainageEquipmentList : drainageEquipmentList, - drainageEquipmentExcelList : [] + DrainageEquipment: { + isInit: false, + upHeadListDrainageEquipment: upHeadListDrainageEquipment, + drainageEquipmentList: drainageEquipmentList, + drainageEquipmentExcelList: [] } } /** * 데이터 폼 포맷설정 */ -export const dataFormSettingPlan=(item : planFcltyListProps, decmials : number) => { - DataForm({ type: 'float'+decmials, column: 'provisionalApproval', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float'+decmials, column: 'now', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float'+decmials, column: 'stepOne', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float'+decmials, column: 'stepTwo', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float'+decmials, column: 'stepThree', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float'+decmials, column: 'stepFour', item: item as unknown as Record<string, unknown>}); +export const dataFormSettingPlan = (item: planFcltyListProps, decmials: number) => { + DataForm({ type: 'float' + decmials, column: 'provisionalApproval', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float' + decmials, column: 'now', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float' + decmials, column: 'stepOne', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float' + decmials, column: 'stepTwo', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float' + decmials, column: 'stepThree', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float' + decmials, column: 'stepFour', item: item as unknown as Record<string, unknown> }); } -export const dataFormSetting=(item : OperationalStatusListProps)=> { +export const dataFormSetting = (item: OperationalStatusListProps) => { DataForm({ type: 'float0', column: 'yearAvg', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'yearMax', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'yearMin', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'summerAll', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'summerThree', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'summerRain', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'winterAll', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'winterSunnyAvg', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'winterSunnyMax', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'winterSunnyMin', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'groundWaterAvg', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'groundWaterSummer', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'groundWaterWinter', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'rainRate', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'rdi', item: item as unknown as Record<string, unknown>}); + DataForm({ type: 'float0', column: 'yearMax', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'yearMin', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'summerAll', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'summerThree', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'summerRain', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'winterAll', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'winterSunnyAvg', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'winterSunnyMax', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'winterSunnyMin', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'groundWaterAvg', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'groundWaterSummer', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'groundWaterWinter', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'rainRate', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'rdi', item: item as unknown as Record<string, unknown> }); }; -export const dataFormSettingWqlty=(item : OperationalStatusListProps)=> { +export const dataFormSettingWqlty = (item: OperationalStatusListProps) => { DataForm({ type: 'float1', column: 'yearAvg', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float1', column: 'yearMax', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'yearMin', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'summerAll', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'summerThree', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'summerRain', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'winterAll', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'winterSunnyAvg', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'winterSunnyMax', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'winterSunnyMin', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'groundWaterAvg', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'groundWaterSummer', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'groundWaterWinter', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'rainRate', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'rdi', item: item as unknown as Record<string, unknown>}); + DataForm({ type: 'float1', column: 'yearMax', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'yearMin', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'summerAll', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'summerThree', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'summerRain', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'winterAll', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'winterSunnyAvg', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'winterSunnyMax', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'winterSunnyMin', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'groundWaterAvg', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'groundWaterSummer', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'groundWaterWinter', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'rainRate', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'rdi', item: item as unknown as Record<string, unknown> }); }; -const changeValue=(operationalStatus, data, keyTxt = 'INFL_AMNT')=>{ - operationalStatus.yearAvg = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.yearData?Number(Math.round(data.yearData['AVG_'+keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.yearData['AVG_'+keyTxt] : ''; - operationalStatus.yearMax = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.yearData?Number(Math.round(data.yearData['MAX_'+keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.yearData['MAX_'+keyTxt] : ''; - operationalStatus.yearMin = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.yearData?Number(Math.round(data.yearData['MIN_'+keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.yearData['MIN_'+keyTxt] : ''; - // 여름 - operationalStatus.summerAll = data.summer ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.summer?Number(Math.round(data.summer['AVG_'+keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.summer['AVG_'+keyTxt] : ''; - operationalStatus.summerThree = data.summerRainy ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.summerRainy?Number(Math.round(data.summerRainy["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.summerRainy["AVG_" + keyTxt] : ''; - operationalStatus.summerRain = data.summerSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.summerSunny?Number(Math.round(data.summerSunny["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.summerSunny["AVG_" + keyTxt] : ''; - // 겨울 - operationalStatus.winterAll = data.winter ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.winter?Number(Math.round(data.winter["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.winter["AVG_" + keyTxt] : ''; - operationalStatus.winterSunnyMin = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.winterSunny?Number(Math.round(data.winterSunny["MIN_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.winterSunny["MIN_" + keyTxt] : ''; - operationalStatus.winterSunnyAvg = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.winterSunny?Number(Math.round(data.winterSunny["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.winterSunny["AVG_" + keyTxt] : ''; - operationalStatus.winterSunnyMax = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN"))? data.winterSunny?Number(Math.round(data.winterSunny["MAX_" + keyTxt] * 1000) / 1000).toFixed(3).toString():'': data.winterSunny["MAX_" + keyTxt] : ''; - // 지하수 - - // 오염물질 - // RDII 추정 = 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) - const summerLowFlow = data.summerBod && data.summerBod.INFL_AMNT && data.summerBod.INFL_AMNT != 'null' ? Number(data.summerBod.INFL_AMNT) : 0; - const winterAvgBod = data.winterSunny && data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD && data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD != 'null' ? Number(data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD) : 0; - const summerLowBod = data.summerBod && data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD && data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD != 'null' ? Number(data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD) : 0; - const summerLowRain = data.summerBod && data.summerBod.INFO_RN_DAY && data.summerBod.INFO_RN_DAY != 'null' ? Number(data.summerBod.INFO_RN_DAY) : 0; - operationalStatus.rdi = winterAvgBod === 0 ? 0 : roundUp(summerLowFlow * ((winterAvgBod - summerLowBod) / winterAvgBod), 1); - operationalStatus.rdiRain = summerLowRain; -} +// const changeValue = (operationalStatus, data, keyTxt = 'INFL_AMNT') => { +// //console.log('data.yearData', data.yearData); +// operationalStatus.yearAvg = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.yearData ? Number(Math.round(data.yearData['AVG_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.yearData['AVG_' + keyTxt] : ''; +// operationalStatus.yearMax = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.yearData ? Number(Math.round(data.yearData['MAX_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.yearData['MAX_' + keyTxt] : ''; +// operationalStatus.yearMin = data.yearData ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.yearData ? Number(Math.round(data.yearData['MIN_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.yearData['MIN_' + keyTxt] : ''; +// // 여름 +// operationalStatus.summerAll = data.summer ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.summer ? Number(Math.round(data.summer['AVG_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.summer['AVG_' + keyTxt] : ''; +// operationalStatus.summerThree = data.summerRainy ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.summerRainy ? Number(Math.round(data.summerRainy["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.summerRainy["AVG_" + keyTxt] : ''; +// operationalStatus.summerRain = data.summerSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.summerSunny ? Number(Math.round(data.summerSunny["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.summerSunny["AVG_" + keyTxt] : ''; +// // 겨울 +// operationalStatus.winterAll = data.winter ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.winter ? Number(Math.round(data.winter["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.winter["AVG_" + keyTxt] : ''; +// operationalStatus.winterSunnyMin = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.winterSunny ? Number(Math.round(data.winterSunny["MIN_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.winterSunny["MIN_" + keyTxt] : ''; +// operationalStatus.winterSunnyAvg = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.winterSunny ? Number(Math.round(data.winterSunny["AVG_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.winterSunny["AVG_" + keyTxt] : ''; +// operationalStatus.winterSunnyMax = data.winterSunny ? (keyTxt.includes('_TP') || keyTxt.includes("_TN")) ? data.winterSunny ? Number(Math.round(data.winterSunny["MAX_" + keyTxt] * 1000) / 1000).toFixed(3).toString() : '' : data.winterSunny["MAX_" + keyTxt] : ''; +// // 지하수 -const calcValue = (operationalStatus, data, keyTxt='INFL_ASSCT_BFR_WQLT_TP') => { - // 연간 - operationalStatus.yearAvg = data.yearData && data.yearData['AVG_'+keyTxt] ? Number(Math.round(data.yearData['AVG_'+keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.yearMax = data.yearData && data.yearData['MAX_'+keyTxt] ? Number(Math.round(data.yearData['MAX_'+keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.yearMin = data.yearData && data.yearData['MIN_'+keyTxt] ? Number(Math.round(data.yearData['MIN_'+keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; - // 여름 - operationalStatus.summerAll = data.summer && data.summer['AVG_'+keyTxt] ? Number(Math.round(data.summer['AVG_'+keyTxt] * 0.8 * 1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.summerThree = data.summerRainy && data.summerRainy['AVG_'+keyTxt] ? Number(Math.round(data.summerRainy['AVG_'+keyTxt] * 0.82 * 1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.summerRain = data.summerSunny && data.summerSunny['AVG_'+keyTxt] ? Number(Math.round(data.summerSunny['AVG_'+keyTxt] * 0.74 * 1000) / 1000).toFixed(3).toString() : ''; - // 겨울 - operationalStatus.winterAll = data.winter && data.winter['AVG_'+keyTxt] ? Number(Math.round(data.winter['AVG_'+keyTxt] * 1.16*1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.winterSunnyMin = data.winterSunny && data.winterSunny['MIN_'+keyTxt] ? Number(Math.round(data.winterSunny['MIN_'+keyTxt] * 1.2 * 1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.winterSunnyAvg = data.winterSunny && data.winterSunny['AVG_'+keyTxt] ? Number(Math.round(data.winterSunny['AVG_'+keyTxt] * 1.25 *1000) / 1000).toFixed(3).toString() : ''; - operationalStatus.winterSunnyMax = data.winterSunny && data.winterSunny['MAX_'+keyTxt] ? Number(Math.round(data.winterSunny['MAX_'+keyTxt] * 1.3 *1000) / 1000).toFixed(3).toString() : ''; -} +// // 오염물질 +// // RDII 추정 = 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) +// const summerLowFlow = data.summerBod && data.summerBod.INFL_AMNT && data.summerBod.INFL_AMNT != 'null' ? Number(data.summerBod.INFL_AMNT) : 0; +// const winterAvgBod = data.winterSunny && data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD && data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD != 'null' ? Number(data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD) : 0; +// const summerLowBod = data.summerBod && data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD && data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD != 'null' ? Number(data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD) : 0; +// const summerLowRain = data.summerBod && data.summerBod.INFO_RN_DAY && data.summerBod.INFO_RN_DAY != 'null' ? Number(data.summerBod.INFO_RN_DAY) : 0; +// operationalStatus.rdi = winterAvgBod === 0 ? 0 : roundUp(summerLowFlow * ((winterAvgBod - summerLowBod) / winterAvgBod), 1); +// operationalStatus.rdiRain = summerLowRain; +// } -type RtnFlowType = { - inflAmnt: string | number; - infoRnDay: string | number; - bscFctCd: string; - msrDt: string; -} +// const calcValue = (operationalStatus, data, keyTxt = 'INFL_ASSCT_BFR_WQLT_TP') => { +// // 연간 +// operationalStatus.yearAvg = data.yearData && data.yearData['AVG_' + keyTxt] ? Number(Math.round(data.yearData['AVG_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.yearMax = data.yearData && data.yearData['MAX_' + keyTxt] ? Number(Math.round(data.yearData['MAX_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.yearMin = data.yearData && data.yearData['MIN_' + keyTxt] ? Number(Math.round(data.yearData['MIN_' + keyTxt] * 1000) / 1000).toFixed(3).toString() : ''; +// // 여름 +// operationalStatus.summerAll = data.summer && data.summer['AVG_' + keyTxt] ? Number(Math.round(data.summer['AVG_' + keyTxt] * 0.8 * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.summerThree = data.summerRainy && data.summerRainy['AVG_' + keyTxt] ? Number(Math.round(data.summerRainy['AVG_' + keyTxt] * 0.82 * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.summerRain = data.summerSunny && data.summerSunny['AVG_' + keyTxt] ? Number(Math.round(data.summerSunny['AVG_' + keyTxt] * 0.74 * 1000) / 1000).toFixed(3).toString() : ''; +// // 겨울 +// operationalStatus.winterAll = data.winter && data.winter['AVG_' + keyTxt] ? Number(Math.round(data.winter['AVG_' + keyTxt] * 1.16 * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.winterSunnyMin = data.winterSunny && data.winterSunny['MIN_' + keyTxt] ? Number(Math.round(data.winterSunny['MIN_' + keyTxt] * 1.2 * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.winterSunnyAvg = data.winterSunny && data.winterSunny['AVG_' + keyTxt] ? Number(Math.round(data.winterSunny['AVG_' + keyTxt] * 1.25 * 1000) / 1000).toFixed(3).toString() : ''; +// operationalStatus.winterSunnyMax = data.winterSunny && data.winterSunny['MAX_' + keyTxt] ? Number(Math.round(data.winterSunny['MAX_' + keyTxt] * 1.3 * 1000) / 1000).toFixed(3).toString() : ''; +// } -type RtnFlowResponse = { - data: RtnFlowType[]; +type wtrUsgYearsType = { + YEAR: string; + WTR_USG: number; +} +type wtrUsgYearsResponse = { + data: wtrUsgYearsType[]; count: number; } const searchWaterUsage = async (searchYear: string, fcltyCd: string) => { - const waterUsage = [0, 0, 0]; - - for (let i = 0; i < 3; i++) { - try { - const res = await getFetch('fo', '/fclt/flow/' + fcltyCd + '/' + (Number(searchYear)-i)) as RtnFlowResponse; - let totWaterUsage = 0; - - if (res && res.data && Array.isArray(res.data)) { - res.data.forEach((item: RtnFlowType) => { - totWaterUsage += Number(item.inflAmnt); - }); - - if (res.count > 0) { - const avg = totWaterUsage / res.count; - waterUsage[i] = parseFloat((avg * 0.65).toFixed(2)); - } - } - } catch (error) { - console.error(`Error fetching water usage for year ${Number(searchYear)-i}:`, error); - waterUsage[i] = 0; - } + const waterUsage = [0, 0, 0]; // searchYear, searchYear-1, searchYear-2 + + const res = await getFetch('fo', '/dashBoard/wtrUsgYears/' + searchYear + '/' + fcltyCd) as wtrUsgYearsResponse; + let i = 0; + if (res && res.data && Array.isArray(res.data)) { + res.data.forEach((item: wtrUsgYearsType) => { + waterUsage[i] = item.WTR_USG; + i++; + }); } - + return waterUsage; } -export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) => void)=>DashBoardActions=(set: (fn: (state: DashBoardType) => DashBoardType) => void) => { +export const dashBoardReducer = ( + set: (fn: (state: DashBoardType) => DashBoardType) => void, + get: () => DashBoardType +): DashBoardActions => { return { initializeDashBoardData: async () => { set((state: DashBoardType) => ({ @@ -1301,7 +1354,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) }, /** 시설정보 및 운영현황 - 계획정보 */ - searchPlanInfo : async (targetYear: string, fcltyCd: string) => { + searchPlanInfo: async (targetYear: string, fcltyCd: string) => { set((state: DashBoardType) => ({ ...state, PlanFclty: { @@ -1310,71 +1363,70 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) }, })); let waterUsage = [0, 0, 0]; - waterUsage = await searchWaterUsage(targetYear, fcltyCd); - + waterUsage = await searchWaterUsage(targetYear, fcltyCd); // /fclt/flow/{fcltyCd}/{year} + set((state: DashBoardType) => ({ ...state, - PlanFclty : { + PlanFclty: { ...state.PlanFclty, - waterUsage1 : waterUsage[0] || 0, - waterUsage2 : waterUsage[1] || 0, - waterUsage3 : waterUsage[2] || 0, + waterUsage1: waterUsage[0] || 0, + waterUsage2: waterUsage[1] || 0, + waterUsage3: waterUsage[2] || 0, }, })); - const res = await fetchFacilityPlan(targetYear, fcltyCd, set); - - let minDiff=9999999; + const res = await fetchFacilityPlan(targetYear, fcltyCd, set); // /dc-service/api/v1/nowDmgMtncGnsvy + let minDiff = 9999999; let minIndex; - res.forEach((el:any, idx) => { - if(minDiff>Math.abs(Number(targetYear) - Number(el.planYr))){ - minDiff=Math.abs(Number(targetYear) - Number(el.planYr)); - minIndex=idx; + res.forEach((el: any, idx) => { + if (minDiff > Math.abs(Number(targetYear) - Number(el.planYr))) { + minDiff = Math.abs(Number(targetYear) - Number(el.planYr)); + minIndex = idx; } }); const rtnPlanData = res[minIndex]; planFcltyList.map((el, index) => { - planYearList = []; - planYearList.push(rtnPlanData?rtnPlanData.planYrDt4.toString():""); - planYearList.push(rtnPlanData?rtnPlanData.planYr.toString():""); - planYearList.push(rtnPlanData?rtnPlanData.planYrDc1.toString():""); - planYearList.push(rtnPlanData?rtnPlanData.planYrDc2.toString():""); - planYearList.push(rtnPlanData?rtnPlanData.planYrDc3.toString():""); - planYearList.push(rtnPlanData?rtnPlanData.planYrDc4.toString():""); + planYearList = []; + planYearList.push(rtnPlanData ? rtnPlanData.planYrDt4.toString() : ""); + planYearList.push(rtnPlanData ? rtnPlanData.planYr.toString() : ""); + planYearList.push(rtnPlanData ? rtnPlanData.planYrDc1.toString() : ""); + planYearList.push(rtnPlanData ? rtnPlanData.planYrDc2.toString() : ""); + planYearList.push(rtnPlanData ? rtnPlanData.planYrDc3.toString() : ""); + planYearList.push(rtnPlanData ? rtnPlanData.planYrDc4.toString() : ""); - if(index === 0){ // 증설계획(㎥/일) - el.provisionalApproval = rtnPlanData?rtnPlanData.planSwageQntSumDt4:''; // 기승인4단계 - el.now = rtnPlanData?rtnPlanData.planSwageQntSum:''; // 현재 - el.stepOne = rtnPlanData?rtnPlanData.planSwageQntSumDc1:''; // 1단계 - el.stepTwo = rtnPlanData?rtnPlanData.planSwageQntSumDc2:''; // 2단계 - el.stepThree = rtnPlanData?rtnPlanData.planSwageQntSumDc3:''; // 3단계 - el.stepFour = rtnPlanData?rtnPlanData.planSwageQntSumDc4:''; // 4단계 + if (index === 0) { // 증설계획(㎥/일) + el.provisionalApproval = rtnPlanData ? rtnPlanData.planSwageQntSumDt4 : ''; // 기승인4단계 + el.now = rtnPlanData ? rtnPlanData.planSwageQntSum : ''; // 현재 + el.stepOne = rtnPlanData ? rtnPlanData.planSwageQntSumDc1 : ''; // 1단계 + el.stepTwo = rtnPlanData ? rtnPlanData.planSwageQntSumDc2 : ''; // 2단계 + el.stepThree = rtnPlanData ? rtnPlanData.planSwageQntSumDc3 : ''; // 3단계 + el.stepFour = rtnPlanData ? rtnPlanData.planSwageQntSumDc4 : ''; // 4단계 } - if(index === 1){ // 처리인구(인) - el.provisionalApproval = rtnPlanData?rtnPlanData.swtrmtPpltnCntDt4:''; - el.now = rtnPlanData?rtnPlanData.swtrmtPpltnCnt:''; - el.stepOne = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc1:''; - el.stepTwo = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc2:''; - el.stepThree = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc3:''; - el.stepFour = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc4:''; + if (index === 1) { // 처리인구(인) + el.provisionalApproval = rtnPlanData ? rtnPlanData.swtrmtPpltnCntDt4 : ''; + el.now = rtnPlanData ? rtnPlanData.swtrmtPpltnCnt : ''; + el.stepOne = rtnPlanData ? rtnPlanData.swtrmtPpltnCntDc1 : ''; + el.stepTwo = rtnPlanData ? rtnPlanData.swtrmtPpltnCntDc2 : ''; + el.stepThree = rtnPlanData ? rtnPlanData.swtrmtPpltnCntDc3 : ''; + el.stepFour = rtnPlanData ? rtnPlanData.swtrmtPpltnCntDc4 : ''; } - if(index === 2){ // 처리구역(km²) - el.provisionalApproval = rtnPlanData?rtnPlanData.swtrmtZoneAreaDt4:''; - el.now = rtnPlanData?rtnPlanData.swtrmtZoneArea:''; - el.stepOne = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc1:''; - el.stepTwo = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc2:''; - el.stepThree = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc3:''; - el.stepFour = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc4:''; + if (index === 2) { // 처리구역(km²) + el.provisionalApproval = rtnPlanData ? rtnPlanData.swtrmtZoneAreaDt4 : ''; + el.now = rtnPlanData ? rtnPlanData.swtrmtZoneArea : ''; + el.stepOne = rtnPlanData ? rtnPlanData.swtrmtZoneAreaDc1 : ''; + el.stepTwo = rtnPlanData ? rtnPlanData.swtrmtZoneAreaDc2 : ''; + el.stepThree = rtnPlanData ? rtnPlanData.swtrmtZoneAreaDc3 : ''; + el.stepFour = rtnPlanData ? rtnPlanData.swtrmtZoneAreaDc4 : ''; } - if(index === 4){ // 물사용량 - el.provisionalApproval = rtnPlanData?rtnPlanData.ugwtUseSum:''; + if (index === 4) { // 물사용량 + el.provisionalApproval = rtnPlanData ? rtnPlanData.ugwtUseSum : ''; } - - if(index === 2){ // 처리구역(km²) - dataFormSettingPlan(el, 1); - }else if(index == 4){ // 물사용량 - }else{ + + if (index === 2) { // 처리구역(km²) + dataFormSettingPlan(el, 1); + } else if (index == 4) { // 물사용량 + } else { dataFormSettingPlan(el, 0); } @@ -1392,6 +1444,26 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) /** 시설정보 및 운영현황 - 운영실태 */ searchOperationStatus: async (isExcel: boolean, searchYear: string, fcltyCd: string) => { + const state = get(); + let mainGraphData: ApiRes = { data: [], count: 0 }; + + // 이미 조회된 데이터가 있는지 확인 (연도와 시설코드가 같을 때만) + if (state.RainManage.mainGraphYear === searchYear && state.RainManage.mainGraphFcltyCd === fcltyCd && state.RainManage.mainGraphData) { + mainGraphData = state.RainManage.mainGraphData; + } else { + mainGraphData = await fetchMainGraph(searchYear, fcltyCd); + // 조회된 데이터 저장 + set((state: DashBoardType) => ({ + ...state, + RainManage: { + ...state.RainManage, + mainGraphData: mainGraphData, + mainGraphYear: searchYear, + mainGraphFcltyCd: fcltyCd, + } + })); + } + set((state: DashBoardType) => ({ ...state, OperationStatus: { @@ -1399,498 +1471,345 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) isLoadingOperationStatus: true, }, })); - const res = await fetchOperationStatus(searchYear, fcltyCd); // /dashBoard/mngInfo/ - const data = res.data; - let yearMaxSumFlow = 1; // 연간운영실태 최대 - let yearMinSumFlow = 1; // 연간운영실태 최소 - let yearAvgSumFlow = 1; // 연간운영실태 평균 - let summerAll = 1; // 하절기 전기간 - let summerThree = 1; // 하절기 - let summerRain = 1; // 하절기 - let winterAll = 1; // 동절기 전기간 - let winterSunnyMin = 1; // 동절기 청천 최소 - let winterSunnyAvg = 1; // 동절기 청천 평균 - let winterSunnyMax = 1; // 동절기 청천 최대 - - // 최대, 최소 총 유입량 (1년 하루 합계의 최대, 최소) + // const fcltCdList = [fcltyCd]; + //const wtrUsgRes: any = await fetchWtrUsgYmList(searchYear, fcltCdList); // 수용가의 실제 물사용량 - // 하절기 청천시, 강우시 재계산 리턴값 - let summerChungchunFlow = 1; // 하절기 청천 - let summerChungchunReturnAmnt = 1; // 하절기 청천 - let summerChungchunEfflAmnt = 1; // 하절기 청천 - let summerRainInflAmnt = 1; // 하절기 강우시 - let summerRainReturnAmnt = 1; // 하절기 강우시 - let summerRainEfflAmnt = 1; // 하절기 강우시 - - // 동절기 청천시 최대, 최소, 평균 리턴값 - let winterChungchunFlowAvg = 1; // 동절기 청천 하수유입량 평균 - let winterChungchunFlowMax = 1; // 동절기 청천 하수유입량 최대 - let winterChungchunFlowMin = 1; // 동절기 청천 하수유입량 최소 - let winterChungchunReturnAvg = 1; // 동절기 청천 총인반류수 평균 - let winterChungchunReturnMax = 1; // 동절기 청천 총인반류수 최대 - let winterChungchunReturnMin = 1; // 동절기 청천 총인반류수 최소 - let winterChungchunEfflAvg = 1; // 동절기 청천 방류량 평균 - let winterChungchunEfflMax = 1; // 동절기 청천 방류량 최대 - let winterChungchunEfflMin = 1; // 동절기 청천 방류량 최소 - + const operList = { + inFlow: { + yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 + , summerTot: 0, summerAvg: 0, summerRainy: 0, summerSunny: 0 + , winterTot: 0, winterAvg: 0, winterSunny: 0, winterSunnyMax: 0, winterSunnyMin: 9999999 + , groundTot: 0, groundSummer: 0, groundWinter: 0, rdii: 0, rdiiRainAmt: 0 + }, + inflAmt: { + yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999, yearSunny: 0 + , summerTot: 0, summerAvg: 0, summerRainy: 0, summerSunny: 0 + , winterTot: 0, winterAvg: 0, winterSunny: 0, winterSunnyMax: 0, winterSunnyMin: 9999999 + , groundTot: 0, groundSummer: 0, groundWinter: 0, rdii: 0, rdiiRainAmt: 0 + }, + sumFlow: { + yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 + , summerTot: 0, summerAvg: 0, summerRainy: 0, summerSunny: 0 + , winterTot: 0, winterAvg: 0, winterSunny: 0, winterSunnyMax: 0, winterSunnyMin: 9999999 + , groundTot: 0, groundSummer: 0, groundWinter: 0, rdii: 0, rdiiRainAmt: 0 + }, + returnFlow: { + yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 + , summerTot: 0, summerAvg: 0, summerRainy: 0, summerSunny: 0 + , winterTot: 0, winterAvg: 0, winterSunny: 0, winterSunnyMax: 0, winterSunnyMin: 9999999 + , groundTot: 0, groundSummer: 0, groundWinter: 0, rdii: 0, rdiiRainAmt: 0 + }, + efflFlow: { + yearTot: 0, yearAvg: 0, yearMax: 0, yearMin: 9999999 + , summerTot: 0, summerAvg: 0, summerRainy: 0, summerSunny: 0 + , winterTot: 0, winterAvg: 0, winterSunny: 0, winterSunnyMax: 0, winterSunnyMin: 9999999 + , groundTot: 0, groundSummer: 0, groundWinter: 0, rdii: 0, rdiiRainAmt: 0 + }, + }; const clone = (arr: any[]) => arr?.map(v => ({ ...v })) ?? []; - let rdiPercent = 0; + let rdiPercent = 0; let operationalStatusList = clone(operationalStatusListOrg); - if(data && operationalStatusList){ + if (operationalStatusList) { // 유량 - 유입 if (operationalStatusList?.[0]) { - // 연간 - const yearAvg = data.yearData===null ? '' : ( - (data.yearData.AVG_INFL_AMNT?data.yearData.AVG_INFL_AMNT:0) + // 실제유입량(반류수제거)(m³/일) 평균 - (data.yearData.AVG_SUM_FLOW?data.yearData.AVG_SUM_FLOW:0) + // api 주석처리되어 있음. - (data.yearData.AVG_RETURN_AMNT?data.yearData.AVG_RETURN_AMNT:0) // 반류수유량(m³/일) 평균 - ); - const yearMax = data.yearData===null ? '' : ( - (data.yearData.MAX_INFL_AMNT?data.yearData.MAX_INFL_AMNT:0) + // 실제유입량(반류수제거)(m³/일) 최대 - (data.yearData.MAX_SUM_FLOW?data.yearData.MAX_SUM_FLOW:0) + // api 주석처리되어 있음. - (data.yearData.MAX_RETURN_AMNT?data.yearData.MAX_RETURN_AMNT:0) // 반류수유량(m³/일) 최대 - ); - const yearMin = data.yearData===null ? '' : ( - (data.yearData.MIN_INFL_AMNT?data.yearData.MIN_INFL_AMNT:0) + // 실제유입량(반류수제거)(m³/일) 최소 - (data.yearData.MIN_SUM_FLOW?data.yearData.MIN_SUM_FLOW:0) + // api 주석처리되어 있음. - (data.yearData.MIN_RETURN_AMNT?data.yearData.MIN_RETURN_AMNT:0) // 반류수유량(m³/일) 최소 - ); - operationalStatusList[0].yearAvg = yearAvg; - operationalStatusList[0].yearMax = yearMax; - operationalStatusList[0].yearMin = yearMin; - // 여름 - operationalStatusList[0].summerAll = data.summer === null ? '' : ( - (data.summer.AVG_INFL_AMNT ? data.summer.AVG_INFL_AMNT : 0) + - (data.summer.AVG_SUM_FLOW ? data.summer.AVG_SUM_FLOW : 0) - ); - operationalStatusList[0].summerThree = data.summerRainy === null ? '' : ( // 하절기 강우시 - (data.summerRainy.AVG_INFL_AMNT ? data.summerRainy.AVG_INFL_AMNT : 0) + - (data.summerRainy.AVG_SUM_FLOW ? data.summerRainy.AVG_SUM_FLOW : 0) - ); - operationalStatusList[0].summerRain = data.summerSunny === null ? '' : ( // 하절기 청천시 - (data.summerSunny.AVG_INFL_AMNT ? data.summerSunny.AVG_INFL_AMNT : 0) + - (data.summerSunny.AVG_SUM_FLOW ? data.summerSunny.AVG_SUM_FLOW : 0) - ); - // 겨울 - operationalStatusList[0].winterAll = data.winter === null ? '' : ( // 동절기 전기간 - (data.winter.AVG_INFL_AMNT ? data.winter.AVG_INFL_AMNT : 0) + - (data.winter.AVG_SUM_FLOW ? data.winter.AVG_SUM_FLOW : 0) + - (data.winter.AVG_TP_INFL_WQLT_TP ? data.winter.AVG_TP_INFL_WQLT_TP : 0) - ); - operationalStatusList[0].winterSunnyMin = data.winterSunny === null ? '' : (// 동절기 청천 최소 - (data.winterSunny.MIN_INFL_AMNT ? data.winterSunny.MIN_INFL_AMNT : 0) + - (data.winterSunny.MIN_SUM_FLOW ? data.winterSunny.MIN_SUM_FLOW : 0) + - (data.winterSunny.MIN_TP_INFL_WQLT_TP ? data.winterSunny.MIN_TP_INFL_WQLT_TP : 0) - ); - operationalStatusList[0].winterSunnyAvg = data.winterSunny === null ? '' : (// 동절기 청천 평균 - (data.winterSunny.AVG_INFL_AMNT ? data.winterSunny.AVG_INFL_AMNT : 0) + - (data.winterSunny.AVG_SUM_FLOW ? data.winterSunny.AVG_SUM_FLOW : 0) + - (data.winterSunny.AVG_TP_INFL_WQLT_TP ? data.winterSunny.AVG_TP_INFL_WQLT_TP : 0) - ); - operationalStatusList[0].winterSunnyMax = data.winterSunny === null ? '' : (// 동절기 청천 최대 - (data.winterSunny.MAX_INFL_AMNT ? data.winterSunny.MAX_INFL_AMNT : 0) + - (data.winterSunny.MAX_SUM_FLOW ? data.winterSunny.MAX_SUM_FLOW : 0) + - (data.winterSunny.MAX_TP_INFL_WQLT_TP ? data.winterSunny.MAX_TP_INFL_WQLT_TP : 0) - ); // 연계처리수 - let yearDay = 0; - let summerDay = 0; - let winterDay = 0; - let summerRainDay = 0; - let summerChungchunDay = 0; - let winterChungchuDay = 0; + let yearDay = 0, summerDay = 0, winterDay = 0; // 연간 일수, 하절기 일수, 동절기 일수 + let summerRainDay = 0, summerChungchunDay = 0, winterChungchuDay = 0; // 하절기 강우 일수, 하절기 청천 일수, 동절기 청천 일수 - let totFlowMax = 0; - let totFlowMin = 9999999; - - let yearSumFlow = 0; - let maxYearSumFlow = 0; - let minYearSumFlow = 9999999; - let summerSumFlow = 0; - let summerChungchunSumFlow = 0; - let summerRainDaySumFlow = 0; - let summerRainDayFlow = 0; - let summerRainDayReturnFlow = 0; - let summerRainDayEfflFlow = 0; - - let winterSumFlow = 0; - let winterChungchunSumFlow = 0; - let minWinterChungchunSumFlow = 9999999; - let maxWinterChungchunSumFlow = 0; - // 연계처리수,지하수유입량 const bscFctCd = fcltyCd; - const res2 = await fetchFlowRateModalList(bscFctCd, searchYear); // /fclt/flow/ - yearDay = res2.data?res2.data.length:1; - res2.data.forEach((item: any) => { - yearSumFlow += item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; - if(maxYearSumFlow < (item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0)) { - maxYearSumFlow = item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; - } - if(minYearSumFlow > (item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0)) { - minYearSumFlow = item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; + let res2: any = { data: [] }; + + // 이미 조회된 데이터가 있는지 확인 (연도와 시설코드가 같을 때만) + if (state.RainManage.flowRateYear === searchYear && state.RainManage.flowRateFcltyCd === fcltyCd && state.RainManage.flowRateData) { + res2 = state.RainManage.flowRateData; + } else { + res2 = await fetchFlowRateModalList(bscFctCd, searchYear); // /fclt/flow/ - 일별 유량 조회 + // 조회된 데이터 저장 + set((state: DashBoardType) => ({ + ...state, + RainManage: { + ...state.RainManage, + flowRateData: res2, + flowRateYear: searchYear, + flowRateFcltyCd: fcltyCd, + } + })); + } + + //const calData = CalcRainEffect(clone(res2.data), 'SUNNY'); + //const calRainData = CalcRainEffect(clone(res2.data), 'RAIN'); + + let cntSunnyDay = 0; // 청천일 수 + // let chungcunSumFlow = 0; // 연간 청천 순수하수 + + let rainEffectDay = 0; + yearDay = res2.data ? res2.data.length : 1; + res2.data.forEach((item: any) => { // --일별 유량 start + + const month = item.msrDt.substring(4, 6); // 월 추출 + const sumFlow = item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; + const inflAmnt = item.inflAmnt && item.inflAmnt !== 'x' ? Number(item.inflAmnt.toString().trim()) : 0; + const returnAmnt = item.returnAmnt && item.returnAmnt !== 'x' ? Number(item.returnAmnt.toString().trim()) : 0; + const efflAmnt = item.efflAmnt && item.efflAmnt !== 'x' ? Number(item.efflAmnt.toString().trim()) : 0; + + if (item.infoRnDay == null) item.infoRnDay = 0; + if (item.infoRnDay >= 3) { + rainEffectDay = 2; + // 강우시 + } else { + rainEffectDay--; } - // 1년 최대 총 하수유입량 - let totYearFlow = 0; - totYearFlow = (item.inflAmnt && item.inflAmnt !== 'x' ? Number(item.inflAmnt.toString().trim()) : 0) + (item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0) + (item.returnAmnt && item.returnAmnt !== 'x' ? Number(item.returnAmnt.toString().trim()) : 0); - if(totFlowMax < totYearFlow) { - totFlowMax = totYearFlow; - } - if(totFlowMin > totYearFlow || totFlowMin === 0) { - totFlowMin = totYearFlow; + if (rainEffectDay >= 0) { + // 강우영향일 + if (month === '06' || month === '07' || month === '08') { // 하절기 강우영향일 + summerRainDay += 1; + operList.inFlow.summerRainy += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.summerRainy += inflAmnt; + operList.sumFlow.summerRainy += sumFlow; + operList.returnFlow.summerRainy += returnAmnt; + operList.efflFlow.summerRainy += efflAmnt; + } + } else if (rainEffectDay < 0) { + cntSunnyDay += 1; // 청천일수 + // 청천시 + // chungcunSumFlow += inflAmnt; // 연간 청천 순수하수 + operList.inflAmt.yearSunny += inflAmnt; // 연간 청천 순수하수 + + if (month === '06' || month === '07' || month === '08') { // 하절기 청천 + summerChungchunDay += 1; + operList.inFlow.summerSunny += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.summerSunny += inflAmnt; + operList.sumFlow.summerSunny += sumFlow; + operList.returnFlow.summerSunny += returnAmnt; + operList.efflFlow.summerSunny += efflAmnt; + } else if (month === '01' || month === '02') { // 동절기 청천 + winterChungchuDay += 1; + const inflowOne = inflAmnt + sumFlow + returnAmnt; + operList.inFlow.winterSunny += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.winterSunny += inflAmnt; + operList.sumFlow.winterSunny += sumFlow; + operList.returnFlow.winterSunny += returnAmnt; + operList.efflFlow.winterSunny += efflAmnt; + + if (operList.inFlow.winterSunnyMax < inflowOne) operList.inFlow.winterSunnyMax = inflowOne; + if (operList.inflAmt.winterSunnyMax < inflAmnt) operList.inflAmt.winterSunnyMax = inflAmnt; + if (operList.sumFlow.winterSunnyMax < sumFlow) operList.sumFlow.winterSunnyMax = sumFlow; + if (operList.returnFlow.winterSunnyMax < returnAmnt) operList.returnFlow.winterSunnyMax = returnAmnt; + if (operList.efflFlow.winterSunnyMax < efflAmnt) operList.efflFlow.winterSunnyMax = efflAmnt; + + if (operList.inFlow.winterSunnyMin > inflowOne) operList.inFlow.winterSunnyMin = inflowOne; + if (operList.inflAmt.winterSunnyMin > inflAmnt) operList.inflAmt.winterSunnyMin = inflAmnt; + if (operList.sumFlow.winterSunnyMin > sumFlow) operList.sumFlow.winterSunnyMin = sumFlow; + if (operList.returnFlow.winterSunnyMin > returnAmnt) operList.returnFlow.winterSunnyMin = returnAmnt; + if (operList.efflFlow.winterSunnyMin > efflAmnt) operList.efflFlow.winterSunnyMin = efflAmnt; + } } - if(item.msrDt.substring(4,6) === '06' || item.msrDt.substring(4,6) === '07' || item.msrDt.substring(4,6) === '08') { + const inflowOne = inflAmnt + sumFlow + returnAmnt; + operList.inFlow.yearTot += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.yearTot += inflAmnt; + operList.sumFlow.yearTot += sumFlow; + operList.returnFlow.yearTot += returnAmnt; + operList.efflFlow.yearTot += efflAmnt; + + if (operList.inFlow.yearMax < inflowOne) operList.inFlow.yearMax = inflowOne; + if (operList.inflAmt.yearMax < inflAmnt) operList.inflAmt.yearMax = inflAmnt; + if (operList.sumFlow.yearMax < sumFlow) operList.sumFlow.yearMax = sumFlow; + if (operList.returnFlow.yearMax < returnAmnt) operList.returnFlow.yearMax = returnAmnt; + if (operList.efflFlow.yearMax < efflAmnt) operList.efflFlow.yearMax = efflAmnt; + + if (operList.inFlow.yearMin > inflowOne) operList.inFlow.yearMin = inflowOne; + if (operList.inflAmt.yearMin > inflAmnt) operList.inflAmt.yearMin = inflAmnt; + if (operList.sumFlow.yearMin > sumFlow) operList.sumFlow.yearMin = sumFlow; + if (operList.returnFlow.yearMin > returnAmnt) operList.returnFlow.yearMin = returnAmnt; + if (operList.efflFlow.yearMin > efflAmnt) operList.efflFlow.yearMin = efflAmnt; + + if (month === '06' || month === '07' || month === '08') { summerDay += 1; - summerSumFlow += item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; - } else if(item.msrDt.substring(4,6) === '01' || item.msrDt.substring(4,6) === '02') { + operList.inFlow.summerTot += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.summerTot += inflAmnt; + operList.sumFlow.summerTot += sumFlow; + operList.returnFlow.summerTot += returnAmnt; + operList.efflFlow.summerTot += efflAmnt; + } else if (month === '01' || month === '02') { winterDay += 1; - winterSumFlow += item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; + operList.inFlow.winterTot += inflAmnt + sumFlow + returnAmnt; + operList.inflAmt.winterTot += inflAmnt; + operList.sumFlow.winterTot += sumFlow; + operList.returnFlow.winterTot += returnAmnt; + operList.efflFlow.winterTot += efflAmnt; } - }); + }); // --일별 유량 end - const calData = CalcRainEffect(clone(res2.data), 'SUNNY'); - const calRainData = CalcRainEffect(clone(res2.data), 'RAIN'); - - // 지하수유입량 - 청천시 기준 - let chungcunSumFlow = 0; - let sumSummerChungcunFlow = 0; - let sumWinterChungcunFlow = 0; - - // 하절기 청천시 총인설비 반류수 - let summerChungchunDayReturnAmnt = 0; - // 하절기 청천시 방류수 - let summerChungchunDayEfflAmnt = 0; + // 연간 평균 + operList.inFlow.yearAvg = (yearDay > 0) ? operList.inFlow.yearTot / yearDay : 0; + operList.inflAmt.yearAvg = (yearDay > 0) ? operList.inflAmt.yearTot / yearDay : 0; + operList.sumFlow.yearAvg = (yearDay > 0) ? operList.sumFlow.yearTot / yearDay : 0; + operList.returnFlow.yearAvg = (yearDay > 0) ? operList.returnFlow.yearTot / yearDay : 0; + operList.efflFlow.yearAvg = (yearDay > 0) ? operList.efflFlow.yearTot / yearDay : 0; + // 하절기 전기간 평균 + operList.inFlow.summerAvg = (summerDay > 0) ? operList.inFlow.summerTot / summerDay : 0; + operList.inflAmt.summerAvg = (summerDay > 0) ? operList.inflAmt.summerTot / summerDay : 0; + operList.sumFlow.summerAvg = (summerDay > 0) ? operList.sumFlow.summerTot / summerDay : 0; + operList.returnFlow.summerAvg = (summerDay > 0) ? operList.returnFlow.summerTot / summerDay : 0; + operList.efflFlow.summerAvg = (summerDay > 0) ? operList.efflFlow.summerTot / summerDay : 0; + // 하절기 강우시 평균 + operList.inFlow.summerRainy = (summerRainDay > 0) ? operList.inFlow.summerRainy / summerRainDay : 0; + operList.inflAmt.summerRainy = (summerRainDay > 0) ? operList.inflAmt.summerRainy / summerRainDay : 0; + operList.sumFlow.summerRainy = (summerRainDay > 0) ? operList.sumFlow.summerRainy / summerRainDay : 0; + operList.returnFlow.summerRainy = (summerRainDay > 0) ? operList.returnFlow.summerRainy / summerRainDay : 0; + operList.efflFlow.summerRainy = (summerRainDay > 0) ? operList.efflFlow.summerRainy / summerRainDay : 0; + // 하절기 청천시 평균 + operList.inFlow.summerSunny = (summerChungchunDay > 0) ? operList.inFlow.summerSunny / summerChungchunDay : 0; + operList.inflAmt.summerSunny = (summerChungchunDay > 0) ? operList.inflAmt.summerSunny / summerChungchunDay : 0; + operList.sumFlow.summerSunny = (summerChungchunDay > 0) ? operList.sumFlow.summerSunny / summerChungchunDay : 0; + operList.returnFlow.summerSunny = (summerChungchunDay > 0) ? operList.returnFlow.summerSunny / summerChungchunDay : 0; + operList.efflFlow.summerSunny = (summerChungchunDay > 0) ? operList.efflFlow.summerSunny / summerChungchunDay : 0; + // 동절기 전기간 평균 + operList.inFlow.winterAvg = (winterDay > 0) ? operList.inFlow.winterTot / winterDay : 0; + operList.inflAmt.winterAvg = (winterDay > 0) ? operList.inflAmt.winterTot / winterDay : 0; + operList.sumFlow.winterAvg = (winterDay > 0) ? operList.sumFlow.winterTot / winterDay : 0; + operList.returnFlow.winterAvg = (winterDay > 0) ? operList.returnFlow.winterTot / winterDay : 0; + operList.efflFlow.winterAvg = (winterDay > 0) ? operList.efflFlow.winterTot / winterDay : 0; + // 동절기 청천시 평균 + operList.inFlow.winterSunny = (winterChungchuDay > 0) ? operList.inFlow.winterSunny / winterChungchuDay : 0; + operList.inflAmt.winterSunny = (winterChungchuDay > 0) ? operList.inflAmt.winterSunny / winterChungchuDay : 0; + operList.sumFlow.winterSunny = (winterChungchuDay > 0) ? operList.sumFlow.winterSunny / winterChungchuDay : 0; + operList.returnFlow.winterSunny = (winterChungchuDay > 0) ? operList.returnFlow.winterSunny / winterChungchuDay : 0; + operList.efflFlow.winterSunny = (winterChungchuDay > 0) ? operList.efflFlow.winterSunny / winterChungchuDay : 0; - // 동절기 총 유입량 최대, 최소 - let winterChungchunTotalInflAmntMax = 0 ; - let winterChungchunTotalInflAmntMin = 9999999 ; - // 동절기 청천시 하수유입량 최대, 최소 - let winterChungchunDayFlowMax = 0 ; - let winterChungchunDayFlowMin = 9999999 ; - // 동절기 청천시 총인반류수 최대, 최소, 평균 - let winterChungchunDayReturnMax = 0 ; - let winterChungchunDayReturnMin = 9999999 ; - let winterChungchunDayReturnAvg = 0 ; - // 동절기 청천시 방류량 최대, 최소, 평균 - let winterChungchunDayEfflMax = 0 ; - let winterChungchunDayEfflMin = 9999999 ; - let winterChungchunDayEfflAvg = 0; - - // 하절기, 동절기 청천시 계산 - calData.forEach((flowItem: any) => { - chungcunSumFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - - if(flowItem.msrDt.substring(4,6) ==='06' || flowItem.msrDt.substring(4,6) ==='07' || flowItem.msrDt.substring(4,6) ==='08'){ - summerChungchunDay += 1; - summerChungchunSumFlow += flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0; - sumSummerChungcunFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - summerChungchunDayReturnAmnt += flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0; - summerChungchunDayEfflAmnt += flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0; - - }else if(flowItem.msrDt.substring(4,6) ==='01' || flowItem.msrDt.substring(4,6) ==='02'){ - winterChungchuDay += 1; - winterChungchunSumFlow += flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0; - sumWinterChungcunFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - winterChungchunDayReturnAvg += flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0; - winterChungchunDayEfflAvg += flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0; - - let totWinterTotalInflAmnt = 0; - totWinterTotalInflAmnt = (flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0) + (flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0) + (flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0); - if(winterChungchunTotalInflAmntMax < totWinterTotalInflAmnt) { - winterChungchunTotalInflAmntMax = totWinterTotalInflAmnt; - } - if(winterChungchunTotalInflAmntMin > totWinterTotalInflAmnt || winterChungchunTotalInflAmntMin === 0) { - winterChungchunTotalInflAmntMin = totWinterTotalInflAmnt; - } - - if(winterChungchunDayFlowMin > (flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0)) { - winterChungchunDayFlowMin = flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - } - if(winterChungchunDayFlowMax < (flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0)) { - winterChungchunDayFlowMax = flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - } - - if(minWinterChungchunSumFlow > (flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0) ) { - minWinterChungchunSumFlow = flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0; - } - if(maxWinterChungchunSumFlow < (flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0)) { - maxWinterChungchunSumFlow = flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0; - } - - if(winterChungchunDayReturnMin > (flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0)) { - winterChungchunDayReturnMin = flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0; - } - if(winterChungchunDayReturnMax < (flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0)) { - winterChungchunDayReturnMax = flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0; - } - - if(winterChungchunDayEfflMin > (flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0)) { - winterChungchunDayEfflMin = flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0; - } - if(winterChungchunDayEfflMax < (flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0)) { - winterChungchunDayEfflMax = flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0; - } - } - }); - - // 하절기 강우시 계산 - calRainData.forEach((flowItem: any) => { - if(flowItem.msrDt.substring(4,6) === '06' || flowItem.msrDt.substring(4,6) === '07' || flowItem.msrDt.substring(4,6) === '08') { - summerRainDay += 1; - summerRainDaySumFlow += flowItem.sumFlow && flowItem.sumFlow !== 'x' ? Number(flowItem.sumFlow.toString().trim()) : 0; - summerRainDayFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; - summerRainDayReturnFlow += flowItem.returnAmnt && flowItem.returnAmnt !== 'x' ? Number(flowItem.returnAmnt.toString().trim()) : 0; - summerRainDayEfflFlow += flowItem.efflAmnt && flowItem.efflAmnt !== 'x' ? Number(flowItem.efflAmnt.toString().trim()) : 0; - } - }); - - // 연계처리수 - yearAvgSumFlow = Number((yearSumFlow&&Number(yearSumFlow)!=0)?(yearSumFlow / yearDay):''); - yearMaxSumFlow = maxYearSumFlow; - yearMinSumFlow = minYearSumFlow; - summerAll = summerSumFlow&&Number(summerSumFlow)!=0?Number(summerSumFlow / summerDay):0 - summerThree= summerRainDaySumFlow&&Number(summerRainDaySumFlow)!=0?Number(summerRainDaySumFlow / summerRainDay):0; - summerRain = summerChungchunSumFlow&&Number(summerChungchunSumFlow)!=0?Number(summerChungchunSumFlow / summerChungchunDay):0; - winterAll = winterSumFlow&&Number(winterSumFlow)!=0?Number(winterSumFlow / winterDay):0; - winterSunnyMin = minWinterChungchunSumFlow; - winterSunnyAvg = winterChungchunSumFlow&&Number(winterChungchunSumFlow)!=0?(winterChungchunSumFlow / winterChungchuDay):0; - winterSunnyMax = maxWinterChungchunSumFlow; - - // 하절기 강우시 계산 - // 하수유입량 (강우시) - summerRainInflAmnt = summerRainDayFlow&&Number(summerRainDayFlow)!=0?Number(summerRainDayFlow / summerRainDay):0; - // 총인설비방류수 (강우시) - summerRainReturnAmnt = summerRainDayReturnFlow&&Number(summerRainDayReturnFlow)!=0?Number(summerRainDayReturnFlow / summerRainDay):0; - // 방루량 (강우시) - summerRainEfflAmnt = summerRainDayEfflFlow&&summerRainDayEfflFlow!=0?summerRainDayEfflFlow / summerRainDay:0; - - // 동절기 청천시 계산 - // 하수유입량 (청천시) 평균 - winterChungchunFlowAvg = sumWinterChungcunFlow&&Number(sumWinterChungcunFlow)!=0?(sumWinterChungcunFlow / winterChungchuDay):0; - // 총인반류수 (청천시) 평균 - winterChungchunReturnAvg = winterChungchunDayReturnAvg&&Number(winterChungchunDayReturnAvg)!=0?(winterChungchunDayReturnAvg / winterChungchuDay):0; - // 방류량 (청천시) 평균 - winterChungchunEfflAvg = winterChungchunDayEfflAvg&&Number(winterChungchunDayEfflAvg)!=0?(winterChungchunDayEfflAvg / winterChungchuDay):0; - // 하수유입량 (청천시) 최대 - winterChungchunFlowMax = winterChungchunDayFlowMax; - // 총인반류수 (청천시) 최대 - winterChungchunReturnMax = winterChungchunDayReturnMax; - // 방류량 (청천시) 최대 - winterChungchunEfflMax = winterChungchunDayEfflMax; - // 하수유입량 (청천시) 최소 - winterChungchunFlowMin = winterChungchunDayFlowMin; - // 총인반류수 (청천시) 최소 - winterChungchunReturnMin = winterChungchunDayReturnMin; - // 방류량 (청천시) 최소 - winterChungchunEfflMin = winterChungchunDayEfflMin - - operationalStatusList[0].yearAvg = roundUp(Number(operationalStatusList[0].yearAvg), 0) + roundUp(yearAvgSumFlow, 0); - operationalStatusList[0].yearMax = roundUp(totFlowMax, 0); // + roundUp(yearMaxSumFlow, 0); - operationalStatusList[0].yearMin = roundUp(totFlowMin, 0); - operationalStatusList[0].summerAll = roundUp(Number(operationalStatusList[0].summerAll), 0) + roundUp(summerAll, 0); - operationalStatusList[0].summerThree = roundUp(Number(operationalStatusList[0].summerThree), 0) + roundUp(summerThree, 0); - operationalStatusList[0].summerRain = roundUp(Number(operationalStatusList[0].summerRain), 0) + roundUp(summerRain, 0); - operationalStatusList[0].winterAll = roundUp(Number(operationalStatusList[0].winterAll), 0) + roundUp(winterAll, 0); - operationalStatusList[0].winterSunnyMin = roundUp(winterChungchunTotalInflAmntMin, 0); - operationalStatusList[0].winterSunnyMax = roundUp(winterChungchunTotalInflAmntMax, 0); - operationalStatusList[0].winterSunnyAvg = roundUp(Number(operationalStatusList[0].winterSunnyAvg), 0) + roundUp(winterSunnyAvg, 0); + // 연간 유입 평균 = 하수유입량(순수하수유입량) + 연계처리량 + 반류수유량(m³/일) + operationalStatusList[0].yearAvg = roundUp(operList.inFlow.yearAvg, 0); // 연간 유입 평균 = 하수유입량(순수하수유입량) + 연계처리량 + 반류수유량(m³/일) + operationalStatusList[0].yearMax = roundUp(operList.inFlow.yearMax, 0); // 연간 유입 최대 + operationalStatusList[0].yearMin = roundUp(operList.inFlow.yearMin, 0); // 연간 유입 최소 + operationalStatusList[0].summerAll = roundUp(operList.inFlow.summerAvg, 0); // 하절기 유입 전기간 + operationalStatusList[0].summerThree = roundUp(operList.inFlow.summerRainy, 0); // 하절기 유입 강우시(강우영향일) + operationalStatusList[0].summerRain = roundUp(operList.inFlow.summerSunny, 0); // 하절기 유입 청천시 + operationalStatusList[0].winterAll = roundUp(operList.inFlow.winterAvg, 0); // 동절기 유입 전기간 + operationalStatusList[0].winterSunnyMin = roundUp(operList.inFlow.winterSunnyMin, 0); // 동절기 유입 최소 + operationalStatusList[0].winterSunnyMax = roundUp(operList.inFlow.winterSunnyMax, 0); // 동절기 유입 최대 + operationalStatusList[0].winterSunnyAvg = roundUp(operList.inFlow.winterAvg, 0); // 동절기 유입 평균 + // 청천시 유입(순수하수) 평균 + const usage = operList.inflAmt.yearSunny / cntSunnyDay; // 연간 청천 순수하수 평균 + const summerUsage = operList.inflAmt.summerSunny; // 하절기 청천 순수하수 평균 + const winterUsage = operList.inflAmt.winterSunny; // 동절기 청천 순수하수 평균 + // 물사용량 = 유입평균 * 0.65 + const wtrUsg = operList.inflAmt.yearAvg * 0.65; // 연간 물사용량 평균 + const wtrUsgSummer = operList.inflAmt.summerAvg * 0.65; // 하절기 물사용량 평균 + const wtrUsgWinter = operList.inflAmt.winterAvg * 0.65; // 동절기 물사용량 평균 // 지하수유입량 - const usage = chungcunSumFlow / calData.length; - const summerUsage = sumSummerChungcunFlow / summerChungchunDay; - const winterUsage = sumWinterChungcunFlow / winterChungchuDay; - - // 임시 계산변수값들 메인에 대입 - summerChungchunFlow = summerUsage; - summerChungchunReturnAmnt = summerChungchunDayReturnAmnt / summerChungchunDay; - summerChungchunEfflAmnt = summerChungchunDayEfflAmnt / summerChungchunDay; - - operationalStatusList[0].groundWaterAvg = Math.round((usage-(usage*0.65*OSU_PERCENT)) * 100) / 100; - operationalStatusList[0].groundWaterSummer = Math.round((summerUsage-(summerUsage*0.65*OSU_PERCENT)) * 100) / 100; - operationalStatusList[0].groundWaterWinter = Math.round((winterUsage-(winterUsage*0.65*OSU_PERCENT)) * 100) / 100; - - //operationalStatusList[0].groundWaterAvg = calData.length === 0 ? '' : Number(calData[0].inflAmnt) - (Number(calData[0].inflAmnt)*0.65*0.8); - //operationalStatusList[0].groundWaterSummer = calData.length === 0 ? '' : Number(calData[0].inflAmnt) - (Number(calData[0].inflAmnt)*0.65*0.8); - //operationalStatusList[0].groundWaterWinter = calData.length === 0 ? '' : Number(calData[0].inflAmnt) - (Number(calData[0].inflAmnt)*0.65*0.8); + operationalStatusList[0].groundWaterAvg = Math.round((usage - (wtrUsg * OSU_PERCENT)) * 100) / 100; + operationalStatusList[0].groundWaterSummer = Math.round((summerUsage - (wtrUsgSummer * OSU_PERCENT)) * 100) / 100; + operationalStatusList[0].groundWaterWinter = Math.round((winterUsage - (wtrUsgWinter * OSU_PERCENT)) * 100) / 100; // 오염물질 // RDII 추정 = 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) - const resp = await fetchMainGraph(searchYear, fcltyCd); - const wqltData = resp.data.wqltObjs; - const wqltDataTot = CalcRainEffect(clone(wqltData)); - const wqltDataSunny = CalcRainEffect(clone(wqltData), 'SUNNY'); + // const resp = await fetchMainGraph(searchYear, fcltyCd); + const resp = mainGraphData; + if (resp && resp.data && resp.data.wqltObjs && resp.data.wqltObjs.length > 0) { + const wqltData = resp.data.wqltObjs; + const wqltDataTot = CalcRainEffect(clone(wqltData)); + const wqltDataSunny = CalcRainEffect(clone(wqltData), 'SUNNY'); - let minSummerBod = 99999999; - let minSummerBodInfl = 99999999; - let winterCnt = 0; - let winterBodSunny = 0; - let rdiRain = 0; + let minSummerBod = 99999999; + let minSummerBodInfl = 99999999; + let winterCnt = 0; + let winterBodSunny = 0; + let rdiRain = 0; - const summerMonth = ['06', '07', '08']; - const winterMonth = ['01', '02']; + const summerMonth = ['06', '07', '08']; + const winterMonth = ['01', '02']; - wqltDataTot.filter(item=>summerMonth.includes(item.msrDt.toString().replaceAll('-','').substring(4,6))).forEach(item2 => { - if(Number(item2.infoRnDay)>=3){ - const inflBod = Number(item2.inflAssctBfrWqltBod); - const infl = Number(item2.inflAmnt); - const rain = Number(item2.infoRnDay); - if(inflBod < minSummerBod){ - minSummerBodInfl = infl; - minSummerBod = inflBod; - rdiRain = rain; + wqltDataTot.filter(item => summerMonth.includes(item.msrDt.toString().replaceAll('-', '').substring(4, 6))).forEach(item2 => { + if (Number(item2.infoRnDay) >= 3) { // 3mm 이상 강우일 + const inflBod = Number(item2.inflAssctBfrWqltBod); //유입연계전수질_BOD(mg/L) + const infl = Number(item2.inflAmnt); //실제유입량(반류수제거)(m³/일) + const rain = Number(item2.infoRnDay); //강우량 + if (inflBod < minSummerBod) { + minSummerBodInfl = infl; + minSummerBod = inflBod; + rdiRain = rain; // 하절기 최소 bod일때의 일강수량 + } } - } - }); + }); - wqltDataSunny.filter(item=>winterMonth.includes(item.msrDt.toString().replaceAll('-','').substring(4,6))).forEach(item2 => { - winterBodSunny += Number(item2.inflAssctBfrWqltBod); - winterCnt += 1; - }); + wqltDataSunny.filter(item => winterMonth.includes(item.msrDt.toString().replaceAll('-', '').substring(4, 6))).forEach(item2 => { + winterBodSunny += Number(item2.inflAssctBfrWqltBod); // 동절기 청천시 유입BOD(mg/L) + winterCnt += 1; + }); - const roundMinSummerBodInfl = Number(roundNumber(minSummerBodInfl, 0).toString().replaceAll(',','')); - const roundMinSummerBod = Number(roundNumber(minSummerBod, 1).toString().replaceAll(',','')); - const roundWinterBodSunny = Number(roundNumber(winterBodSunny, 1).toString().replaceAll(',','')); + const roundMinSummerBodInfl = Number(roundNumber(minSummerBodInfl, 0).toString().replaceAll(',', '')); + const roundMinSummerBod = Number(roundNumber(minSummerBod, 1).toString().replaceAll(',', '')); + const roundWinterBodSunny = Number(roundNumber(winterBodSunny, 1).toString().replaceAll(',', '')); - const winterBodAvg = Number(roundNumber((roundWinterBodSunny / winterCnt), 1).toString().replaceAll(',','')); - // 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) - const winMinusSum = winterBodAvg - roundMinSummerBod; + const winterBodAvg = Number(roundNumber((roundWinterBodSunny / winterCnt), 1).toString().replaceAll(',', '')); + // 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) + const winMinusSum = winterBodAvg - roundMinSummerBod; - const rdiValue = winMinusSum===0 ? 0 : roundMinSummerBodInfl * (winMinusSum / winterBodAvg ); - - rdiPercent = (rdiValue / minSummerBodInfl) * 100; + const rdiValue = winMinusSum === 0 ? 0 : roundMinSummerBodInfl * (winMinusSum / winterBodAvg); - operationalStatusList[0].rdi = rdiValue; - operationalStatusList[0].rdiRain = rdiRain; - /* const summerLowFlow = data.summerBod&&data.summerBod.INFL_AMNT&&data.summerBod.INFL_AMNT!='null'?Number(data.summerBod.INFL_AMNT):0; - const winterAvgBod = data.winterSunny&&data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD&&data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD!='null'?Number(data.winterSunny.AVG_INFL_ASSCT_BFR_WQLT_BOD):0; - const summerLowBod = data.summerBod&&data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD&&data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD!='null'?Number(data.summerBod.MIN_INFL_ASSCT_BFR_WQLT_BOD):0; - const summerLowRain = data.summerBod&&data.summerBod.INFO_RN_DAY&&data.summerBod.INFO_RN_DAY!='null'?Number(data.summerBod.INFO_RN_DAY):0; - operationalStatusList[0].rdi = winterAvgBod===0?0:roundUp(summerLowFlow * ((winterAvgBod - summerLowBod) / winterAvgBod), 2); - operationalStatusList[0].rdiRain = summerLowRain; */ - } + rdiPercent = (rdiValue / minSummerBodInfl) * 100; - if(operationalStatusList[0] && operationalStatusList[7]){ - operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData && data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg.toString().replaceAll(',', '')) / (data.yearData && data.yearData.AVG_INFL_AMNT ? Number(data.yearData.AVG_INFL_AMNT.toString().replaceAll(',','')) : 0) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer && data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer.toString().replaceAll(',','')) / (data.summer && data.summer.AVG_INFL_AMNT ? Number(data.summer.AVG_INFL_AMNT.toString().replaceAll(',','')) : 0) * 100) * 10) / 10+'%'; - operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter && data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter.toString().replaceAll(',','')) / (data.winter && data.winter.AVG_INFL_AMNT ? Number(data.winter.AVG_INFL_AMNT.toString().replaceAll(',','')) : 0) * 100) * 10) / 10+'%'; - operationalStatusList[7].rdi = roundNumber(rdiPercent, 1)+'%'; - /* operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : Number(Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT)) * 100; - operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : Number(Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT)) * 100; - operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : Number(Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT)) * 100;*/ - } - - for(let i=1; i<operationalStatusList.length; i++){ - const txtArray = ['1', 'INFL_AMNT', 'SUM_FLOW', 'RETURN_AMNT', 'EFFL_AMNT', 'INFL_ASSCT_BFR_WQLT_BOD', 'EFFL_WQLT_BOD', 'INFL_ASSCT_BFR_WQLT_TN', 'EFFL_WQLT_TN', 'INFL_ASSCT_BFR_WQLT_TP', 'EFFL_WQLT_TP', 'TP_INFL_WQLT_TP', 'EFFL_WQLT_TP']; - if(operationalStatusList[i]){ - if(i < 7){ - changeValue(operationalStatusList[i], data, txtArray[i]); - if(i < 5){ - dataFormSetting(operationalStatusList[i]!); - }else{ - dataFormSettingWqlty(operationalStatusList[i]!); - } - - }else{ - calcValue(operationalStatusList[i], data, txtArray[i]); - } + operationalStatusList[0].rdi = rdiValue; // RDII 추정 + operationalStatusList[0].rdiRain = rdiRain; // RDII 추정시 일강수량(mm) } } - // 순수하수량 - if(operationalStatusList[1]){ - operationalStatusList[1].summerThree = summerRainInflAmnt; - operationalStatusList[1].summerRain = summerChungchunFlow; - operationalStatusList[1].winterSunnyAvg = winterChungchunFlowAvg; - operationalStatusList[1].winterSunnyMax = winterChungchunFlowMax; - operationalStatusList[1].winterSunnyMin = winterChungchunFlowMin; - dataFormSetting(operationalStatusList[1]); + if (operationalStatusList[0] && operationalStatusList[7]) { + // operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : operList.inFlow.yearTot && data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg.toString().replaceAll(',', '')) / (data.yearData && data.yearData.AVG_INFL_AMNT ? Number(data.yearData.AVG_INFL_AMNT.toString().replaceAll(',', '')) : 0) * 100) * 10) / 10 + '%'; + // operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer && data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer.toString().replaceAll(',', '')) / (data.summer && data.summer.AVG_INFL_AMNT ? Number(data.summer.AVG_INFL_AMNT.toString().replaceAll(',', '')) : 0) * 100) * 10) / 10 + '%'; + // operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter && data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter.toString().replaceAll(',', '')) / (data.winter && data.winter.AVG_INFL_AMNT ? Number(data.winter.AVG_INFL_AMNT.toString().replaceAll(',', '')) : 0) * 100) * 10) / 10 + '%'; + operationalStatusList[7].groundWaterAvg = operList.inflAmt.yearAvg === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg.toString().replaceAll(',', '')) / operList.inflAmt.yearAvg * 100) * 10) / 10 + '%'; + operationalStatusList[7].groundWaterSummer = operList.inflAmt.summerAvg === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer.toString().replaceAll(',', '')) / operList.inflAmt.summerAvg * 100) * 10) / 10 + '%'; + operationalStatusList[7].groundWaterWinter = operList.inflAmt.winterAvg === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter.toString().replaceAll(',', '')) / operList.inflAmt.winterAvg * 100) * 10) / 10 + '%'; + operationalStatusList[7].rdi = roundNumber(rdiPercent, 1) + '%'; } - // 연계처리수 - if(operationalStatusList[2]){ - operationalStatusList[2].yearAvg = yearAvgSumFlow; - operationalStatusList[2].yearMax = yearMaxSumFlow; - operationalStatusList[2].yearMin = yearMinSumFlow; - operationalStatusList[2].summerAll = summerAll; - operationalStatusList[2].summerThree = summerThree; - operationalStatusList[2].summerRain = summerRain; - operationalStatusList[2].winterAll = winterAll; - operationalStatusList[2].winterSunnyMin = winterSunnyMin; - operationalStatusList[2].winterSunnyAvg = winterSunnyAvg; - operationalStatusList[2].winterSunnyMax = winterSunnyMax; - dataFormSetting(operationalStatusList[2]); - } + // 순수하수량(1), 연계처리수(2), 총인설비방류수(3), 방류수(4) 데이터 매핑 + const flowMappings = [ + { index: 0, source: operList.inFlow }, + { index: 1, source: operList.inflAmt }, + { index: 2, source: operList.sumFlow }, + { index: 3, source: operList.returnFlow }, + { index: 4, source: operList.efflFlow }, + ]; + flowMappings.forEach(({ index, source }) => { + const item = operationalStatusList[index]; + if (item) { + item.yearAvg = source.yearAvg; + item.yearMax = source.yearMax; + item.yearMin = source.yearMin; + item.summerAll = source.summerAvg; + item.summerThree = source.summerRainy; + item.summerRain = source.summerSunny; + item.winterSunnyAvg = source.winterSunny; + item.winterSunnyMax = source.winterSunnyMax; + item.winterSunnyMin = source.winterSunnyMin; + item.winterAll = source.winterAvg; + dataFormSetting(item); + } + }); - // 총인설비방류수 - if(operationalStatusList[3]){ - operationalStatusList[3].summerThree = summerRainReturnAmnt; - operationalStatusList[3].summerRain = summerChungchunReturnAmnt; - operationalStatusList[3].winterSunnyAvg = winterChungchunReturnAvg; - operationalStatusList[3].winterSunnyMax = winterChungchunReturnMax; - operationalStatusList[3].winterSunnyMin = winterChungchunReturnMin; - dataFormSetting(operationalStatusList[3]); - } - - // 방류수 - if(operationalStatusList[4]){ - operationalStatusList[4].summerThree = summerRainEfflAmnt; - operationalStatusList[4].summerRain = summerChungchunEfflAmnt; - operationalStatusList[4].winterSunnyAvg = winterChungchunEfflAvg; - operationalStatusList[4].winterSunnyMax = winterChungchunEfflMax; - operationalStatusList[4].winterSunnyMin = winterChungchunEfflMin; - dataFormSetting(operationalStatusList[4]); - } - - if (operationalStatusList[0]) { - // 안전하게 숫자 변환하는 함수 - const toNum = (val) => { - if (val === null || val === undefined || val === '' || val === '0') return 0; - return Number(val.toString().replaceAll(',', '')) || 0; - }; - - operationalStatusList[0].summerAll = - toNum(operationalStatusList[1]?.summerAll) + - toNum(operationalStatusList[2]?.summerAll) + - toNum(operationalStatusList[3]?.summerAll); - - operationalStatusList[0].summerThree = - toNum(operationalStatusList[1]?.summerThree) + - toNum(operationalStatusList[2]?.summerThree) + - toNum(operationalStatusList[3]?.summerThree); - - operationalStatusList[0].summerRain = - toNum(operationalStatusList[1]?.summerRain) + - toNum(operationalStatusList[2]?.summerRain) + - toNum(operationalStatusList[3]?.summerRain); - - operationalStatusList[0].winterAll = - toNum(operationalStatusList[1]?.winterAll) + - toNum(operationalStatusList[2]?.winterAll) + - toNum(operationalStatusList[3]?.winterAll); - - operationalStatusList[0].winterSunnyAvg = - toNum(operationalStatusList[1]?.winterSunnyAvg) + - toNum(operationalStatusList[2]?.winterSunnyAvg) + - toNum(operationalStatusList[3]?.winterSunnyAvg); - - dataFormSetting(operationalStatusList[0]); - } } - const rtnData = await calcWaterQltyData(operationalStatusList, searchYear, fcltyCd); + const rtnData = await calcWaterQltyData(operationalStatusList, searchYear, fcltyCd, mainGraphData); // 수질 데이터 청천시, 강우시 재계산 + let tpFctYn = 'N'; + if (mainGraphData.data.wqltObjs && mainGraphData.data.wqltObjs.length > 0) + tpFctYn = mainGraphData.data.wqltObjs?.[0].tpFctYn; // 총인설비가 없을 경우 제외 - console.log('총인시설 여부 : ', data.yearData?.TP_FCT_YN); + console.log('총인시설 여부 : ', tpFctYn); - console.log('rtnData : ', rtnData); - if(data.yearData?.TP_FCT_YN !== 'Y') { + // console.log('rtnData : ', rtnData); + if (tpFctYn !== 'Y') { operationalStatusList = rtnData.slice(0, 11); - }else{ + } else { operationalStatusList = rtnData; } set((state: DashBoardType) => ({ @@ -1898,13 +1817,33 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) OperationStatus: { ...state.OperationStatus, operationalStatusList: operationalStatusList, - operationalStatusCount: res.count, - isLoadingOperationStatus : false, + operationalStatusCount: 0, + isLoadingOperationStatus: false, }, })) }, - + searchMainGraph: async (searchYear: string, fcltyCd: string) => { + const state = get(); + let res: any = null; + + // 이미 조회된 데이터가 있는지 확인 + if (state.RainManage.mainGraphYear === searchYear && state.RainManage.mainGraphFcltyCd === fcltyCd && state.RainManage.mainGraphData) { + res = state.RainManage.mainGraphData; + } else { + res = await fetchMainGraph(searchYear, fcltyCd); + // 데이터 저장 + set((state: DashBoardType) => ({ + ...state, + RainManage: { + ...state.RainManage, + mainGraphData: res, + mainGraphYear: searchYear, + mainGraphFcltyCd: fcltyCd, + } + })); + } + set((state: DashBoardType) => ({ ...state, MainGraph: { @@ -1912,7 +1851,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) isLoadingMainGraph: true, }, })); - const res = await fetchMainGraph(searchYear, fcltyCd); + // const res = await fetchMainGraph(searchYear, fcltyCd); const res2 = await fetchFacilityWaterQltyInfo(fcltyCd); let chungcunSumFlow = 0; @@ -1920,14 +1859,14 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) calData.forEach((flowItem: any) => { chungcunSumFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0; }); - const usage = chungcunSumFlow&&chungcunSumFlow>0?chungcunSumFlow / calData.length:0; - + const usage = chungcunSumFlow && chungcunSumFlow > 0 ? chungcunSumFlow / calData.length : 0; + //bscFctCd === '11200PB001R') 중랑 - 테스트할 때 코드넣고 해야 보기편해요~ /* if(bscFctCd === '11200PB001R') { console.log('calData.length : ', calData.length); console.log('usage : ', usage, 'summerUsage : ', summerUsage, 'winterUsage : ', winterUsage); } */ - const underWaterInputYear = Math.round((usage-(usage*0.65*OSU_PERCENT)) * 1) / 1; + const underWaterInputYear = Math.round((usage - (usage * 0.65 * OSU_PERCENT)) * 1) / 1; // 법적방류수질 데이터 const sortedWaterQltyInfo = [...res2].sort((a, b) => { @@ -1935,17 +1874,17 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) const numA = Number(a.extnsSeqno); const numB = Number(b.extnsSeqno); if (!isNaN(numA) && !isNaN(numB)) { - return numA - numB; + return numA - numB; } // 숫자 변환이 실패하면 문자열로 비교 return String(a.extnsSeqno).localeCompare(String(b.extnsSeqno)); }); - const waterQltyStandartd : any = sortedWaterQltyInfo && sortedWaterQltyInfo.length > 0 + const waterQltyStandartd: any = sortedWaterQltyInfo && sortedWaterQltyInfo.length > 0 ? sortedWaterQltyInfo[sortedWaterQltyInfo.length - 1] : []; - if(res == null || res.data == null || res.data.length === 0) { + if (res == null || res.data == null || res.data.length === 0) { set((state: DashBoardType) => ({ ...state, MainGraph: { @@ -1959,61 +1898,61 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) chart_efflTn: [], chart_inflBod: [], chart_efflBod: [], - waterQltyStandartd : [], - isLoadingMainGraph : false, + waterQltyStandartd: [], + isLoadingMainGraph: false, } })); return; } - + const chart_inflowRain = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), barVal0: item.infoRnDay ?? 0, lineVal0: underWaterInputYear, lineVal1: item.inflAmnt ?? 0, })); - + const chart_bodRain = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), barVal0: item.infoRnDay ?? 0, lineVal0: item.inflAssctBfrWqltBod ?? 0, })); const chart_inflTp = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.inflAssctBfrWqltTp ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.inflAssctBfrWqltTp ?? 0 })); - + const chart_chongInTp = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : Number(item.tpInflWqltTp) == 0 ? null : item.tpInflWqltTp + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: Number(item.tpInflWqltTp) == 0 ? null : item.tpInflWqltTp })); - + const chart_efflTp = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.efflWqltTp ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.efflWqltTp ?? 0 })); const chart_inflTn = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.inflAssctBfrWqltTn ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.inflAssctBfrWqltTn ?? 0 })); const chart_efflTn = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.efflWqltTn ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.efflWqltTn ?? 0 })); const chart_inflBod = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.inflAssctBfrWqltBod ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.inflAssctBfrWqltBod ?? 0 })); - + const chart_efflBod = res.data.wqltObjs.map((item: FacilityWaterQltyProps) => ({ - label: item.msrDt.substring(0, 4)+"-"+item.msrDt.substring(4, 6)+"-"+item.msrDt.substring(6, 8), - val : item.efflWqltBod ?? 0 + label: item.msrDt.substring(0, 4) + "-" + item.msrDt.substring(4, 6) + "-" + item.msrDt.substring(6, 8), + val: item.efflWqltBod ?? 0 })); - + set((state: DashBoardType) => ({ ...state, MainGraph: { @@ -2021,19 +1960,55 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) chart_inflowRain: chart_inflowRain, chart_bodRain: chart_bodRain, chart_inflTp: chart_inflTp, - chart_chongInTp : chart_chongInTp, + chart_chongInTp: chart_chongInTp, chart_efflTp: chart_efflTp, chart_inflTn: chart_inflTn, chart_efflTn: chart_efflTn, chart_inflBod: chart_inflBod, chart_efflBod: chart_efflBod, - waterQltyStandartd : waterQltyStandartd, - isLoadingMainGraph : false, + waterQltyStandartd: waterQltyStandartd, + isLoadingMainGraph: false, }, })) }, - searchRainManage: async (searchYear: string, fcltyCd: string, isExcel : boolean) => { + searchRainManage: async (searchYear: string, fcltyCd: string, isExcel: boolean) => { + const state = get(); + let res: any = null; + + if (state.RainManage.mainGraphYear === searchYear && state.RainManage.mainGraphFcltyCd === fcltyCd && state.RainManage.mainGraphData) { + res = state.RainManage.mainGraphData; + } else { + res = await fetchMainGraph(searchYear, fcltyCd); + set((state: DashBoardType) => ({ + ...state, + RainManage: { + ...state.RainManage, + mainGraphData: res, + mainGraphYear: searchYear, + mainGraphFcltyCd: fcltyCd, + } + })); + } + + let res2: any = { data: [] }; + // 이미 조회된 데이터가 있는지 확인 (연도와 시설코드가 같을 때만) + if (state.RainManage.flowRateYear === searchYear && state.RainManage.flowRateFcltyCd === fcltyCd && state.RainManage.flowRateData) { + res2 = state.RainManage.flowRateData; + } else { + res2 = await fetchFlowRateModalList(fcltyCd, searchYear); // /fclt/flow/ - 일별 유량 조회 + // 조회된 데이터 저장 + set((state: DashBoardType) => ({ + ...state, + RainManage: { + ...state.RainManage, + flowRateData: res2, + flowRateYear: searchYear, + flowRateFcltyCd: fcltyCd, + } + })); + } + set((state: DashBoardType) => ({ ...state, RainManage: { @@ -2041,14 +2016,14 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) isLoadingRainManage: true, }, })); - const res = await fetchMainGraph(searchYear, fcltyCd); - if(res == null || res.data.wqltObjs == null || res.data.wqltObjs.length === 0) { + // const res = await fetchMainGraph(searchYear, fcltyCd); + if (res == null || res.data.wqltObjs == null || res.data.wqltObjs.length === 0) { set((state: DashBoardType) => ({ ...state, RainManage: { ...state.RainManage, rainManageList: [], - isLoadingRainManage : false, + isLoadingRainManage: false, } })); return; @@ -2057,10 +2032,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) const itemList = ['계', '3~10mm 미만', '10~20mm 미만', '20~30mm 미만', '30~40mm 미만', '40~50mm 미만', '50~60mm 미만', '60mm 이상']; const rainRate = ['0', '0', '0', '0', '0', '0', '0', '0']; - const rainDate = [0, 0, 0, 0, 0, 0, 0, 0]; - const inflAmtCnt = [0, 0, 0, 0, 0, 0, 0, 0]; - const inflAmnt = [0, 0, 0, 0, 0, 0, 0, 0]; - const fcltyCpct = [0, 0, 0, 0, 0, 0, 0, 0]; + const rainDate: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAmnt: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const fcltyCpct: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; const dsInflWqltBod: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; const dsInflWqltCod: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; const dsInflWqltSs: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; @@ -2092,7 +2066,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) const rainEffectList = CalcRainEffect(res.data.wqltObjs, 'RAIN_DAY'); // Process the data and create rain management list - const totItemCnt = rainEffectList?rainEffectList.length : 1; + const totItemCnt = rainEffectList ? rainEffectList.length : 1; let totInflAssctBfrWqltBod = 0; let totInflAssctBfrWqltToc = 0; let totInflAssctBfrWqltSs = 0; @@ -2103,6 +2077,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) let totEfflWqltSs = 0; let totEfflWqltTn = 0; let totEfflWqltTp = 0; + let totInflAmnt = 0; rainEffectList.forEach((item: RainManageListProps) => { totInflAssctBfrWqltBod += item.inflAssctBfrWqltBod ? Number(item.inflAssctBfrWqltBod.toString().trim()) : 0; @@ -2115,13 +2090,13 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) totEfflWqltSs += item.efflWqltSs ? Number(item.efflWqltSs.toString().trim()) : 0; totEfflWqltTn += item.efflWqltTn ? Number(item.efflWqltTn.toString().trim()) : 0; totEfflWqltTp += item.efflWqltTp ? Number(item.efflWqltTp.toString().trim()) : 0; + totInflAmnt += item.inflAmnt ? Number(item.inflAmnt.toString().trim()) : 0; - const toBeRain = item.toBeInfoRnDay ? Number(item.toBeInfoRnDay) : 0; + const toBeRain = item.toBeInfoRnDay ? Number(item.toBeInfoRnDay) : 0; - if(toBeRain >= 3 && toBeRain < 10){ - rainDate[1] = Number(rainDate[1] || 0) + 1; - inflAmtCnt[1] = Number(inflAmtCnt[1] || 0) + 1; - inflAmnt[1] = Number(inflAmnt[1] || 0) + Number(item.inflAmnt || 0); + if (toBeRain >= 3 && toBeRain < 10) { + rainDate[1] = (rainDate[1] || 0) + 1; + inflAmnt[1] = (inflAmnt[1] || 0) + Number(item.inflAmnt || 0); fcltyCpct[1] = 0; inflAssctBfrWqltBodCnt[1] = Number(inflAssctBfrWqltBodCnt[1] || 0) + 1; inflAssctBfrWqltBod[1] = Number(inflAssctBfrWqltBod[1] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2134,7 +2109,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) inflAssctBfrWqltTpCnt[1] = Number(inflAssctBfrWqltTpCnt[1] || 0) + 1; inflAssctBfrWqltTp[1] = Number(inflAssctBfrWqltTp[1] || 0) + Number(item.inflAssctBfrWqltTp || 0); efflWqltBodCnt[1] = Number(efflWqltBodCnt[1] || 0) + 1; - efflWqltBod[1] = Number(efflWqltBod[1] || 0) + Number(item.efflWqltBod || 0); + efflWqltBod[1] = Number(efflWqltBod[1] || 0) + Number(item.efflWqltBod || 0); efflWqltTocCnt[1] = Number(efflWqltTocCnt[1] || 0) + 1; efflWqltToc[1] = Number(efflWqltToc[1] || 0) + Number(item.efflWqltToc || 0); efflWqltSsCnt[1] = Number(efflWqltSsCnt[1] || 0) + 1; @@ -2143,10 +2118,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[1] = Number(efflWqltTn[1] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[1] = Number(efflWqltTpCnt[1] || 0) + 1; efflWqltTp[1] = Number(efflWqltTp[1] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 10 && toBeRain < 20){ - rainDate[2] = Number(rainDate[2] || 0) + 1; - inflAmtCnt[2] = Number(inflAmtCnt[2] || 0) + 1; - inflAmnt[2] = Number(inflAmnt[2] || 0) + Number(item.inflAmnt || 0); + } else if (toBeRain >= 10 && toBeRain < 20) { + rainDate[2] = (rainDate[2] || 0) + 1; + inflAmnt[2] = (inflAmnt[2] || 0) + Number(item.inflAmnt || 0); fcltyCpct[2] = 0; inflAssctBfrWqltBodCnt[2] = Number(inflAssctBfrWqltBodCnt[2] || 0) + 1; inflAssctBfrWqltBod[2] = Number(inflAssctBfrWqltBod[2] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2168,10 +2142,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[2] = Number(efflWqltTn[2] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[2] = Number(efflWqltTpCnt[2] || 0) + 1; efflWqltTp[2] = Number(efflWqltTp[2] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 20 && toBeRain < 30){ - rainDate[3] = Number(rainDate[3] || 0) + 1; - inflAmtCnt[3] = Number(inflAmtCnt[3] || 0) + 1; - inflAmnt[3] = Number(inflAmnt[3] || 0) + Number(item.inflAmnt || 0); + } else if (toBeRain >= 20 && toBeRain < 30) { + rainDate[3] = (rainDate[3] || 0) + 1; + inflAmnt[3] = (inflAmnt[3] || 0) + Number(item.inflAmnt || 0); fcltyCpct[3] = 0; inflAssctBfrWqltBodCnt[3] = Number(inflAssctBfrWqltBodCnt[3] || 0) + 1; inflAssctBfrWqltBod[3] = Number(inflAssctBfrWqltBod[3] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2193,11 +2166,10 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[3] = Number(efflWqltTn[3] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[3] = Number(efflWqltTpCnt[3] || 0) + 1; efflWqltTp[3] = Number(efflWqltTp[3] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 30 && toBeRain < 40){ - rainDate[4] = Number(rainDate[4] || 0) + 1; - inflAmtCnt[4] = Number(inflAmtCnt[4] || 0) + 1; - inflAmnt[4] = Number(inflAmnt[4] || 0) + Number(item.inflAmnt || 0); - fcltyCpct[4] = item.inflAmnt&&Number(item.inflAmnt) !== 0 ? Number(Math.round(Number(item.inflAmnt) / Number(item.fctCpc) * 10) / 10) : 0; + } else if (toBeRain >= 30 && toBeRain < 40) { + rainDate[4] = (rainDate[4] || 0) + 1; + inflAmnt[4] = (inflAmnt[4] || 0) + Number(item.inflAmnt || 0); + fcltyCpct[4] = item.inflAmnt && Number(item.inflAmnt) !== 0 ? Number(Math.round(Number(item.inflAmnt) / Number(item.fctCpc) * 10) / 10) : 0; inflAssctBfrWqltBodCnt[4] = Number(inflAssctBfrWqltBodCnt[4] || 0) + 1; inflAssctBfrWqltBod[4] = Number(inflAssctBfrWqltBod[4] || 0) + Number(item.inflAssctBfrWqltBod || 0); inflAssctBfrWqltTocCnt[4] = Number(inflAssctBfrWqltTocCnt[4] || 0) + 1; @@ -2218,10 +2190,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[4] = Number(efflWqltTn[4] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[4] = Number(efflWqltTpCnt[4] || 0) + 1; efflWqltTp[4] = Number(efflWqltTp[4] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 40 && toBeRain < 50){ - rainDate[5] = Number(rainDate[5] || 0) + 1; - inflAmtCnt[5] = Number(inflAmtCnt[5] || 0) + 1; - inflAmnt[5] = Number(inflAmnt[5] || 0) + Number(item.inflAmnt || 0); + } else if (toBeRain >= 40 && toBeRain < 50) { + rainDate[5] = (rainDate[5] || 0) + 1; + inflAmnt[5] = (inflAmnt[5] || 0) + Number(item.inflAmnt || 0); fcltyCpct[5] = 0; inflAssctBfrWqltBodCnt[5] = Number(inflAssctBfrWqltBodCnt[5] || 0) + 1; inflAssctBfrWqltBod[5] = Number(inflAssctBfrWqltBod[5] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2243,10 +2214,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[5] = Number(efflWqltTn[5] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[5] = Number(efflWqltTpCnt[5] || 0) + 1; efflWqltTp[5] = Number(efflWqltTp[5] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 50 && toBeRain < 60){ - rainDate[6] = Number(rainDate[6] || 0) + 1; - inflAmtCnt[6] = Number(inflAmtCnt[6] || 0) + 1; - inflAmnt[6] = Number(inflAmnt[6] || 0) + Number(item.inflAmnt || 0); + } else if (toBeRain >= 50 && toBeRain < 60) { + rainDate[6] = (rainDate[6] || 0) + 1; + inflAmnt[6] = (inflAmnt[6] || 0) + Number(item.inflAmnt || 0); fcltyCpct[6] = 0; inflAssctBfrWqltBodCnt[6] = Number(inflAssctBfrWqltBodCnt[6] || 0) + 1; inflAssctBfrWqltBod[6] = Number(inflAssctBfrWqltBod[6] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2268,10 +2238,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[6] = Number(efflWqltTn[6] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[6] = Number(efflWqltTpCnt[6] || 0) + 1; efflWqltTp[6] = Number(efflWqltTp[6] || 0) + Number(item.efflWqltTp || 0); - }else if(toBeRain >= 60){ - rainDate[7] = Number(rainDate[7] || 0) + 1; - inflAmtCnt[7] = Number(inflAmtCnt[7] || 0) + 1; - inflAmnt[7] = Number(inflAmnt[7] || 0) + Number(item.inflAmnt || 0); + } else if (toBeRain >= 60) { + rainDate[7] = (rainDate[7] || 0) + 1; + inflAmnt[7] = (inflAmnt[7] || 0) + Number(item.inflAmnt || 0); fcltyCpct[7] = 0; inflAssctBfrWqltBodCnt[7] = Number(inflAssctBfrWqltBodCnt[7] || 0) + 1; inflAssctBfrWqltBod[7] = Number(inflAssctBfrWqltBod[7] || 0) + Number(item.inflAssctBfrWqltBod || 0); @@ -2295,18 +2264,52 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTp[7] = Number(efflWqltTp[7] || 0) + Number(item.efflWqltTp || 0); } }); - + + // 유량 = 순수하수 + 연계처리수 + 총인설비반류수 + const rainEffFlowList: any[] = CalcRainEffect(res2.data, 'RAIN_DAY'); + totInflAmnt = 0; inflAmnt.fill(0); rainDate.fill(0); + rainEffFlowList.forEach((item: any) => { // --일별 유량 start + const toBeRain = item.toBeInfoRnDay ? Number(item.toBeInfoRnDay) : 0; + const inflAmntOne = item.inflAmnt && item.inflAmnt !== 'x' ? Number(item.inflAmnt.toString().trim()) : 0; + const sumFlowOne = item.sumFlow && item.sumFlow !== 'x' ? Number(item.sumFlow.toString().trim()) : 0; + const returnAmntOne = item.returnAmnt && item.returnAmnt !== 'x' ? Number(item.returnAmnt.toString().trim()) : 0; + const inflowOne = inflAmntOne + sumFlowOne + returnAmntOne; + totInflAmnt = totInflAmnt + inflowOne; // 유량합계 + if (toBeRain >= 3 && toBeRain < 10) { + inflAmnt[1] = (inflAmnt[1] || 0) + inflowOne; + rainDate[1] = (rainDate[1] || 0) + 1; + } else if (toBeRain >= 10 && toBeRain < 20) { + inflAmnt[2] = (inflAmnt[2] || 0) + inflowOne; + rainDate[2] = (rainDate[2] || 0) + 1; + } else if (toBeRain >= 20 && toBeRain < 30) { + inflAmnt[3] = (inflAmnt[3] || 0) + inflowOne; + rainDate[3] = (rainDate[3] || 0) + 1; + } else if (toBeRain >= 30 && toBeRain < 40) { + inflAmnt[4] = (inflAmnt[4] || 0) + inflowOne; + rainDate[4] = (rainDate[4] || 0) + 1; + } else if (toBeRain >= 40 && toBeRain < 50) { + inflAmnt[5] = (inflAmnt[5] || 0) + inflowOne; + rainDate[5] = (rainDate[5] || 0) + 1; + } else if (toBeRain >= 50 && toBeRain < 60) { + inflAmnt[6] = (inflAmnt[6] || 0) + inflowOne; + rainDate[6] = (rainDate[6] || 0) + 1; + } else if (toBeRain >= 60) { + inflAmnt[7] = (inflAmnt[7] || 0) + inflowOne; + rainDate[7] = (rainDate[7] || 0) + 1; + } + }); // --일별 유량 end + // Process the data and create rain management list - if(res.data&&res.data.dsWqltObjs&&res.data.dsWqltObjs.length > 0){ + if (res.data && res.data.dsWqltObjs && res.data.dsWqltObjs.length > 0) { // 설계 유입수질 데이터 가져오기 - 0값이 있으면 이전 차수에서 가져오기 const getDesignWaterQualityData = (stepIndex: number, maxPreviousSteps: number = 3) => { for (let i = 0; i <= maxPreviousSteps && stepIndex - i >= 0; i++) { const currentStep = res.data.dsWqltObjs[stepIndex - i]; - if (currentStep && - currentStep.dsInflWqltBod > 0 && - currentStep.dsInflWqltCod > 0 && - currentStep.dsInflWqltSs > 0 && - currentStep.dsInflWqltTn > 0 && + if (currentStep && + currentStep.dsInflWqltBod > 0 && + currentStep.dsInflWqltCod > 0 && + currentStep.dsInflWqltSs > 0 && + currentStep.dsInflWqltTn > 0 && currentStep.dsInflWqltTp > 0) { return { dsInflWqltBod: currentStep.dsInflWqltBod, @@ -2329,23 +2332,23 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) const lastStepIndex = res.data.dsWqltObjs.length - 1; const designData = getDesignWaterQualityData(lastStepIndex); - + dsInflWqltBod[0] = designData.dsInflWqltBod; dsInflWqltCod[0] = designData.dsInflWqltCod; dsInflWqltSs[0] = designData.dsInflWqltSs; dsInflWqltTn[0] = designData.dsInflWqltTn; dsInflWqltTp[0] = designData.dsInflWqltTp; } - - rainDate[0] = (Number(rainDate[1]) || 0) + (Number(rainDate[2]) || 0) + (Number(rainDate[3]) || 0) + - (Number(rainDate[4]) || 0) + (Number(rainDate[5]) || 0) + (Number(rainDate[6]) || 0) + + + rainDate[0] = (Number(rainDate[1]) || 0) + (Number(rainDate[2]) || 0) + (Number(rainDate[3]) || 0) + + (Number(rainDate[4]) || 0) + (Number(rainDate[5]) || 0) + (Number(rainDate[6]) || 0) + (Number(rainDate[7]) || 0); - + // 강수비율 - for(let idx=0; idx<rainDate.length; idx++){ - if(Number(rainDate[idx]) > 0){ + for (let idx = 0; idx < rainDate.length; idx++) { + if (Number(rainDate[idx]) > 0) { rainRate[0] = '100'; - rainRate[idx] = (Math.round(Number(rainDate[idx])/Number(rainDate[0])*100*100)/100).toString(); + rainRate[idx] = (Math.round(Number(rainDate[idx]) / Number(rainDate[0]) * 100 * 100) / 100).toString(); } } @@ -2354,16 +2357,8 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) return count > 0 ? Number((value / count)) : 0; }; - // 데이터 있는 항목만 계산 - let dataIsCount = 0; - for(let idx=0; idx<inflAmnt.length; idx++){ - if(Number(inflAmnt[idx]) > 0){ - dataIsCount++; - } - } - - for(let idx=0; idx<inflAmnt.length; idx++){ - inflAmnt[idx] = calculateAverage(Number(inflAmnt[idx]), Number(inflAmtCnt[idx])); + for (let idx = 1; idx < inflAmnt.length; idx++) { + inflAmnt[idx] = rainDate[idx] ? Math.round((inflAmnt[idx] || 0) / (rainDate[idx] || 0)) : 0; inflAssctBfrWqltBod[idx] = calculateAverage(Number(inflAssctBfrWqltBod[idx]), Number(inflAssctBfrWqltBodCnt[idx])); inflAssctBfrWqltToc[idx] = calculateAverage(Number(inflAssctBfrWqltToc[idx]), Number(inflAssctBfrWqltTocCnt[idx])); inflAssctBfrWqltSs[idx] = calculateAverage(Number(inflAssctBfrWqltSs[idx]), Number(inflAssctBfrWqltSsCnt[idx])); @@ -2381,7 +2376,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) return Number((arr.slice(1).reduce((sum, val) => sum + Number(val), 0))); }; - inflAmnt[0] = calculateAverage(calculateTotal(inflAmnt), dataIsCount); + + // console.log(totInflAmnt); + inflAmnt[0] = Math.round((totInflAmnt / totItemCnt)); // calculateAverage(totInflAmnt, totItemCnt); fcltyCpct[0] = calculateTotal(fcltyCpct); inflAssctBfrWqltBod[0] = Number((Math.round((totInflAssctBfrWqltBod / totItemCnt) * 10) / 10).toFixed(1)); inflAssctBfrWqltToc[0] = Number((Math.round((totInflAssctBfrWqltToc / totItemCnt) * 10) / 10).toFixed(1)); @@ -2393,7 +2390,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltSs[0] = Number((Math.round((totEfflWqltSs / totItemCnt) * 10) / 10).toFixed(1)); efflWqltTn[0] = Number((Math.round((totEfflWqltTn / totItemCnt) * 1000) / 1000).toFixed(3)); efflWqltTp[0] = Number((Math.round((totEfflWqltTp / totItemCnt) * 1000) / 1000).toFixed(3)); - + // 최종 리스트 const filteredData = itemList.map((item, index) => ({ rainItem: item, @@ -2411,15 +2408,15 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) inflAssctBfrWqltSs: inflAssctBfrWqltSs[index] ? (Math.round(inflAssctBfrWqltSs[index] * 10) / 10).toFixed(1) : '', inflAssctBfrWqltTn: inflAssctBfrWqltTn[index] ? (Math.round(inflAssctBfrWqltTn[index] * 1000) / 1000).toFixed(3) : '', inflAssctBfrWqltTp: inflAssctBfrWqltTp[index] ? (Math.round(inflAssctBfrWqltTp[index] * 1000) / 1000).toFixed(3) : '', - efflWqltBod: efflWqltBod[index] ? (Math.round(efflWqltBod[index] * 10) / 10).toFixed(1): '', + efflWqltBod: efflWqltBod[index] ? (Math.round(efflWqltBod[index] * 10) / 10).toFixed(1) : '', efflWqltToc: efflWqltToc[index] ? (Math.round(efflWqltToc[index] * 10) / 10).toFixed(1) : '', efflWqltSs: efflWqltSs[index] ? (Math.round(efflWqltSs[index] * 10) / 10).toFixed(1) : '', efflWqltTn: efflWqltTn[index] ? (Math.round(efflWqltTn[index] * 1000) / 1000).toFixed(3) : '', efflWqltTp: efflWqltTp[index] ? (Math.round(efflWqltTp[index] * 1000) / 1000).toFixed(3) : '' })); - - const excelList : RainManageExcelType[] = []; - if(isExcel){ + + const excelList: RainManageExcelType[] = []; + if (isExcel) { filteredData.forEach((item) => { excelList.push({ 강우량: item.rainItem, @@ -2430,7 +2427,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) 설계유입수질BOD: item.dsInflWqltBod ? (Math.round(Number(item.dsInflWqltBod) * 10) / 10).toFixed(1) : '', 설계유입수질TOC: item.dsInflWqltCod ? (Math.round(Number(item.dsInflWqltCod) * 10) / 10).toFixed(1) : '', 설계유입수질SS: item.dsInflWqltSs ? (Math.round(Number(item.dsInflWqltSs) * 10) / 10).toFixed(1) : '', - 설계유입수질TN: item.dsInflWqltTn ?(Math.round(Number(item.dsInflWqltTn) * 1000) / 1000).toFixed(3) : '', + 설계유입수질TN: item.dsInflWqltTn ? (Math.round(Number(item.dsInflWqltTn) * 1000) / 1000).toFixed(3) : '', 설계유입수질TP: item.dsInflWqltTp ? (Math.round(Number(item.dsInflWqltTp) * 1000) / 1000).toFixed(3) : '', 유입수질BOD: item.inflAssctBfrWqltBod ? (Math.round(Number(item.inflAssctBfrWqltBod) * 10) / 10).toFixed(1) : '', 유입수질TOC: item.inflAssctBfrWqltToc ? (Math.round(Number(item.inflAssctBfrWqltToc) * 10) / 10).toFixed(1) : '', @@ -2445,18 +2442,18 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) }); }); } - + set((state: DashBoardType) => ({ ...state, RainManage: isExcel ? { ...state.RainManage, rainManageList: filteredData as any[], excelList: excelList, - isLoadingRainManage : false, + isLoadingRainManage: false, } : { ...state.RainManage, rainManageList: filteredData as any[], - isLoadingRainManage : false, + isLoadingRainManage: false, } })); }, diff --git a/apps/fo/app/store/facilityPrecipitation/index.tsx b/apps/fo/app/store/facilityPrecipitation/index.tsx index 6272ef55b..b18471eca 100644 --- a/apps/fo/app/store/facilityPrecipitation/index.tsx +++ b/apps/fo/app/store/facilityPrecipitation/index.tsx @@ -153,7 +153,9 @@ export const fetchFacilityRainDetailList = async (searchYear: string|undefined, } }; -const fetchFacilityPrecipitationList = async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { +const fetchFacilityPrecipitationList = async (pageNo: number, pageSize: number, searchYear: string, part?: string + , nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { + const tailStr = '/'+pageNo + '/' + pageSize + '/' + searchYear + (part?'/' + part:'' ) + (nameCd?'/' + nameCd:'' ) + (fcltCpct?'/' + fcltCpct:'' ) + (operationValue?'/' + operationValue:'' ) + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') ; const url = '/fclt/rain' + tailStr; const res = await getFetch('fo', url); diff --git a/apps/fo/app/store/facilityWaterQlty/index.tsx b/apps/fo/app/store/facilityWaterQlty/index.tsx index 0eafcfcd0..676c0624c 100644 --- a/apps/fo/app/store/facilityWaterQlty/index.tsx +++ b/apps/fo/app/store/facilityWaterQlty/index.tsx @@ -2,7 +2,7 @@ import DataForm, { roundNumber } from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; import CalcRainEffect from "@/app/components/common/CalcRainEffect"; -import { COD_YEAR } from "../common"; +import { COD_YEAR } from "../common"; // 2021년 import CommaToNumber from "@/app/components/common/CommaToNumber"; export type { TableUpperProps }; @@ -28,17 +28,19 @@ export interface FacilityRdiiDetailProps { export interface FacilityWaterQltyProps { bscFctCd: string, bscFctNm: string, - msrDt: string - sidoNm: string, - sggNm: string, + msrDt: string, + sido: string, + gugun: string, + // sidoNm: string, + // sggNm: string, addr: string, fctCpc: number, inflAssctBfrWqltBod: string | '', inflAssctAftWqltBod: string | '', inflAssctBfrWqltToc: string | '', inflAssctAftWqltToc: string | '', - inflAssctBfrWqltCod : string | '', - inflAssctAftWqltCod : string | '', + inflAssctBfrWqltCod: string | '', + inflAssctAftWqltCod: string | '', inflAssctBfrWqltSs: string | '', inflAssctAftWqltSs: string | '', inflAssctBfrWqltTn: string | '', @@ -47,14 +49,14 @@ export interface FacilityWaterQltyProps { inflAssctAftWqltTp: string | '', efflWqltBod: string | '', efflWqltToc: string | '', - efflWqltCod : string | '', + efflWqltCod: string | '', efflWqltSs: string | '', efflWqltTn: string | '', efflWqltTp: string | '', - rgnNm:string|'', - rvbsn:string|'', - lgbsn:string|'', - mdmbsn:string|'', + rgnNm: string | '', + rvbsn: string | '', + lgbsn: string | '', + mdmbsn: string | '', inflAmnt: number | '', lclgvrnmntNm: string, infoRnDay: number | string | '', @@ -68,14 +70,14 @@ export interface FacilityWaterQltyProps { tpInflWqltTp: string | '', tpInflWqltEclgyTox: string | '', tpInflWqltClfgrpcnt: string | '', - noProcessInflAmnt : string | number | '', + noProcessInflAmnt: string | number | '', } export interface ExcelType { 시설코드: string, - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', + 권역: string | '', + 단위유역: string | '', + 중권역: string | '', + 환경청: string | '', 시도: string, 시군구: string, 시설이름: string, @@ -146,74 +148,74 @@ const mainLabelArray: string[] = ['구분', '시도', '시군구', '용량별', // 시설수질리스트 헤더(행정별) const upHeadList1: TableUpperProps[] = [ - { id: "C", title: "행정명", colSpan: 2, row:0, widthPercent:10 }, - { id: "sidoNm", title: "시도", row: 1, widthPercent:5}, - { id: "sggNm", title: "시군구", row: 1, widthPercent:5 }, + { id: "C", title: "행정명", colSpan: 2, row: 0, widthPercent: 10 }, + { id: "sido", title: "시도", row: 1, widthPercent: 5 }, + { id: "gugun", title: "시군구", row: 1, widthPercent: 5 }, { id: "bscFctNm", title: "시설명", rowSpan: 2, row: 0, widthPercent: 10 }, { id: "addr", title: "주소", rowSpan: 2, row: 0 }, - { id: "fctCpc", title: "시설용량(m³/일)", align: "right", rowSpan: 2, row: 0, widthPercent:10 }, - { id: "A", title: "유입", colSpan: 5, row: 0, widthPercent:25 }, - { id: "B", title: "방류", colSpan: 5, row: 0, widthPercent:25 }, - { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row: 1, widthPercent:5 }, - { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent:5 }, - { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent:5 }, - { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 4, align: "right", row: 1, widthPercent:5 }, - { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent:5 }, - { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row: 1, widthPercent:5 }, - { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent:5 }, - { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent:5 }, - { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 4, align: "right", row: 1, widthPercent:5 }, - { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent:5 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", rowSpan: 2, row: 0, widthPercent: 10 }, + { id: "A", title: "유입", colSpan: 5, row: 0, widthPercent: 25 }, + { id: "B", title: "방류", colSpan: 5, row: 0, widthPercent: 25 }, + { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 4, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 4, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, ]; // 시설수질리스트 헤더(권역별) const upHeadList2: TableUpperProps[] = [ - { id: "C", title: "유역", colSpan: 3, row: 0, widthPercent:15 }, - { id: "rvbsn", title: "권역", upName: "upChangeRe1", upSequnce: 1, row:1, widthPercent:5 }, - { id: "lgbsn", title: "단위유역", upName: "upChangeRe1", upSequnce: 2, row:1, widthPercent:5 }, - { id: "mdmbsn", title: "중권역", upName: "upChangeRe1", upSequnce: 3, row:1, widthPercent:5 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent:10 }, - { id: "addr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/L)", align: "right", row:0, rowSpan:2, widthPercent:8 }, - { id: "A", title: "유입", colSpan: 5, row: 0, widthPercent:25 }, - { id: "B", title: "방류", colSpan: 5, row: 0, widthPercent:25 }, - { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row:1, widthPercent:5 }, - { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1, widthPercent:5 }, - { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1, widthPercent:5 }, - { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1, widthPercent:5 }, - { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1, widthPercent:5 }, - { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row:1, widthPercent:5 }, - { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1, widthPercent:5 }, - { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1, widthPercent:5 }, - { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1, widthPercent:5 }, - { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1, widthPercent:5 }, + { id: "C", title: "유역", colSpan: 3, row: 0, widthPercent: 15 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe1", upSequnce: 1, row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe1", upSequnce: 2, row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe1", upSequnce: 3, row: 1, widthPercent: 5 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "addr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/L)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "A", title: "유입", colSpan: 5, row: 0, widthPercent: 25 }, + { id: "B", title: "방류", colSpan: 5, row: 0, widthPercent: 25 }, + { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent: 5 }, + { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 5 }, ]; // 시설수질리스트 헤더(환경청별) const upHeadList3: TableUpperProps[] = [ { id: "C", title: "유역", colSpan: 3, row: 0, widthPercent: 15 }, - { id: "rvbsn", title: "권역", row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", row:1, widthPercent: 5 }, - { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 7 }, + { id: "rvbsn", title: "권역", row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 5 }, + { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 7 }, { id: "D", title: "행정명", colSpan: 2, row: 0, widthPercent: 10 }, - { id: "sidoNm", title: "시도", upName: "upChangeRe2", row:1, widthPercent: 5 }, - { id: "sggNm", title: "시군구", upName: "upChangeRe2", row:1, widthPercent: 5 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "addr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/L)", align: "right", row:0, rowSpan:2, widthPercent: 5 }, + { id: "sido", title: "시도", upName: "upChangeRe2", row: 1, widthPercent: 5 }, + { id: "gugun", title: "시군구", upName: "upChangeRe2", row: 1, widthPercent: 5 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "addr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/L)", align: "right", row: 0, rowSpan: 2, widthPercent: 5 }, { id: "A", title: "유입", colSpan: 5, row: 0, widthPercent: 20 }, { id: "B", title: "방류", colSpan: 5, row: 0, widthPercent: 20 }, - { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row:1, widthPercent: 4 }, - { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1, widthPercent: 4 }, - { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1, widthPercent: 4 }, - { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1, widthPercent: 4 }, - { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1, widthPercent: 4 }, - { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row:1, widthPercent: 4 }, - { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1, widthPercent: 4 }, - { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1, widthPercent: 4 }, - { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1, widthPercent: 4 }, - { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1, widthPercent: 4 }, + { id: "inflAssctBfrWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe3", upSequnce: 1, align: "right", row: 1, widthPercent: 4 }, + { id: "inflAssctBfrWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent: 4 }, + { id: "inflAssctBfrWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 4 }, + { id: "inflAssctBfrWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row: 1, widthPercent: 4 }, + { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1, widthPercent: 4 }, + { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row: 1, widthPercent: 4 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent: 4 }, + { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 4 }, + { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1, widthPercent: 4 }, + { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1, widthPercent: 4 }, ]; /** @@ -282,20 +284,24 @@ const upHeadListModalChongIn: TableUpperProps[] = [ ====================================*/ // 메인페이지 // 시설정보 수질 리스트 데이터 호출 -const fetchFacilityWaterQltyList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string|undefined, nameCd?: string|undefined, fcltCpct?: string|undefined, operationValue?: string|undefined, fcltyNm?: string | undefined): Promise<{ data: FacilityWaterQltyProps[], count: number }> => { +const fetchFacilityWaterQltyList = async ( + pageNo: number, pageSize: number, searchYear: string, partCd?: string | undefined + , nameCd?: string | undefined, fcltCpct?: string | undefined + , operationValue?: string | undefined, fcltyNm?: string | undefined +): Promise<{ data: FacilityWaterQltyProps[], count: number, status: number }> => { try { - const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; + const tailStr = partCd ? '/' + partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; const url = '/fclt/qlty/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr; const res = await getFetch('fo', url); - return { data: res.data, count: res.count } + return { data: res.data, count: res.count, status: res.status }; } catch (error) { console.error('시설정보 수질현황 데이터를 가져오는데 실패했습니다:', error); - return { data: [], count: 0 }; + return { data: [], count: 0, status: 300 }; } } // 시설정보 수질 리스트 데이터 호출(모달-수질TMS) -const fetchFacilityWaterQltyTmsList = async (facilityCd: string|undefined) => { +const fetchFacilityWaterQltyTmsList = async (facilityCd: string | undefined) => { try { const res = await getFetch('wr', '/il/getTms/' + facilityCd); return { data: res.data, count: res.count }; @@ -306,7 +312,7 @@ const fetchFacilityWaterQltyTmsList = async (facilityCd: string|undefined) => { }; // 시설정보 수질 리스트 데이터 호출(모달-이력) -export const fetchFacilityWaterQltyListHist = async (searchYear: string|undefined, facilityCd: string|undefined): Promise<WaterQualityHistoryResponse | null> => { +export const fetchFacilityWaterQltyListHist = async (searchYear: string | undefined, facilityCd: string | undefined): Promise<WaterQualityHistoryResponse | null> => { try {// const res = await getFetch('fo', '/fclt/qlty/hist/' + searchYear + '/' + facilityCd); return res; @@ -316,9 +322,9 @@ export const fetchFacilityWaterQltyListHist = async (searchYear: string|undefine } }; // 시설정보 RDII 상세 [{BOD - inflAssctBfrWqltBod, 유입유량 inflAmnt, 강우량 infoRnDay, 측정일 msrDt}] -export const fetchFacilityRdiiDetailList = async (searchYear: string|undefined, swtrmtFcltCdList: string[]): Promise<FacilityRdiiDetailProps | null> => { +export const fetchFacilityRdiiDetailList = async (searchYear: string | undefined, swtrmtFcltCdList: string[]): Promise<FacilityRdiiDetailProps | null> => { try { - const res = await postFetch('fo', '/rdii/detail/'+searchYear, swtrmtFcltCdList); + const res = await postFetch('fo', '/rdii/detail/' + searchYear, swtrmtFcltCdList); return res; } catch (error) { console.error('시설정보 RDII 상세 데이터를 가져오는데 실패했습니다:', error); @@ -345,7 +351,7 @@ export type FacilityWaterQltyType = { FacilityWaterQltyModal: { // 처리시설 수질현황 upHeadListModalYear: TableUpperProps[]; - upHeadListModalYearCod : TableUpperProps[]; + upHeadListModalYearCod: TableUpperProps[]; facilityWaterQltyYearList: FacilityWaterQltyProps[]; upHeadListModalTms: TableUpperProps[]; facilityWaterQltyTmsList: FacilityWaterQltyTmsProps[]; @@ -432,7 +438,8 @@ const dataFormSetting = (item: FacilityWaterQltyProps) => { export type FacilityWaterQltyStore = FacilityWaterQltyType & FacilityWaterQltyActions; -export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType) => FacilityWaterQltyType) => void): FacilityWaterQltyActions => { +type SetFunc = (fn: (state: FacilityWaterQltyType) => FacilityWaterQltyType) => void; +export const facilityWaterQltyReducer = (set: SetFunc): FacilityWaterQltyActions => { return { // 메인페이지 /** @@ -446,7 +453,9 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType * @param operationValue : 운영상태코드 * @param fcltyNm : 시설명 */ - searchFacilityWaterQltyList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { + searchFacilityWaterQltyList: async (pageNo: number, pageSize: number, searchYear: string, part?: string + , nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { + set((state: FacilityWaterQltyType) => ({ ...state, FacilityWaterQltyMain: { @@ -454,64 +463,90 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType isLoading: true, }, })); - const res = await fetchFacilityWaterQltyList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); - const rtnData = res.data.map(item => { + + // const res = await fetchFacilityWaterQltyList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); + //--------- + const waterQltyList: any[] = []; + let recordCount = 0; + if (pageSize < 5000) { + const res = await fetchFacilityWaterQltyList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); + waterQltyList.push(...res.data); + recordCount = res.count; + } else { + for (let idx = 0; idx < pageSize; idx += 100) { + const tmpPageNo = Math.floor(idx / 100) + 1; + const tmpPageSize = Math.min(100, pageSize - idx); + const result = await fetchFacilityWaterQltyList(tmpPageNo, tmpPageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); + if (result?.status === 200 && Array.isArray(result?.data)) { + waterQltyList.push(...result.data); + } + recordCount = result?.count || 0; + if (result?.count < tmpPageNo * tmpPageSize) { + break; // 더 이상 데이터가 없으면 중지 + } + } + } + //------- + + // const rtnData = res.data.map(item => { + const rtnData = waterQltyList.map(item => { dataFormSetting(item) - if(item.inflAssctBfrWqltTn) { - item.inflAssctBfrWqltTn = Number(Math.round(Number(item.inflAssctBfrWqltTn) * 1000)/1000).toFixed(3); + if (item.inflAssctBfrWqltTn) { + item.inflAssctBfrWqltTn = Number(Math.round(Number(item.inflAssctBfrWqltTn) * 1000) / 1000).toFixed(3); } - if(item.efflWqltTn){ - item.efflWqltTn = Number(Math.round(Number(item.efflWqltTn) * 1000)/1000).toFixed(3); + if (item.efflWqltTn) { + item.efflWqltTn = Number(Math.round(Number(item.efflWqltTn) * 1000) / 1000).toFixed(3); } - if(item.inflAssctBfrWqltTp){ - item.inflAssctBfrWqltTp = Number(Math.round(Number(item.inflAssctBfrWqltTp) * 1000)/1000).toFixed(3); + if (item.inflAssctBfrWqltTp) { + item.inflAssctBfrWqltTp = Number(Math.round(Number(item.inflAssctBfrWqltTp) * 1000) / 1000).toFixed(3); } - if(item.efflWqltTp){ - item.efflWqltTp = Number(Math.round(Number(item.efflWqltTp) * 1000)/1000).toFixed(3); + if (item.efflWqltTp) { + item.efflWqltTp = Number(Math.round(Number(item.efflWqltTp) * 1000) / 1000).toFixed(3); } - if(item.inflAssctBfrWqltToc){ - item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltToc) { + item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10) / 10).toFixed(1); } - if(item.efflWqltToc){ - item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); + if (item.efflWqltToc) { + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltBod){ - item.inflAssctBfrWqltBod = Number(Math.round(Number(item.inflAssctBfrWqltBod) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltBod) { + item.inflAssctBfrWqltBod = Number(Math.round(Number(item.inflAssctBfrWqltBod) * 10) / 10).toFixed(1); } - if(item.efflWqltBod){ - item.efflWqltBod = Number(Math.round(Number(item.efflWqltBod) * 10)/10).toFixed(1); + if (item.efflWqltBod) { + item.efflWqltBod = Number(Math.round(Number(item.efflWqltBod) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltSs){ - item.inflAssctBfrWqltSs = Number(Math.round(Number(item.inflAssctBfrWqltSs) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltSs) { + item.inflAssctBfrWqltSs = Number(Math.round(Number(item.inflAssctBfrWqltSs) * 10) / 10).toFixed(1); } - if(item.efflWqltSs){ - item.efflWqltSs = Number(Math.round(Number(item.efflWqltSs) * 10)/10).toFixed(1); + if (item.efflWqltSs) { + item.efflWqltSs = Number(Math.round(Number(item.efflWqltSs) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltToc){ - item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltToc) { + item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10) / 10).toFixed(1); } - if(item.efflWqltToc){ - item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); + if (item.efflWqltToc) { + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10) / 10).toFixed(1); } return item; }); const excelDownList: ExcelType[] = []; - if(pageSize>5000){ - res.data.forEach(item => { + if (pageSize > 5000) { + // res.data.forEach(item => { + waterQltyList.forEach(item => { dataFormSetting(item) item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; - item.inflAssctBfrWqltToc = Number(searchYear)<COD_YEAR? "("+item.inflAssctBfrWqltToc+")" : item.inflAssctBfrWqltToc; - item.efflWqltToc = Number(searchYear)<COD_YEAR? "("+item.efflWqltToc+")" : item.efflWqltToc; + // item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; + // item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; + item.inflAssctBfrWqltToc = Number(searchYear) < COD_YEAR ? "(" + item.inflAssctBfrWqltToc + ")" : item.inflAssctBfrWqltToc; + item.efflWqltToc = Number(searchYear) < COD_YEAR ? "(" + item.efflWqltToc + ")" : item.efflWqltToc; excelDownList.push({ 시설코드: item.bscFctCd, 권역: item.rvbsn, 단위유역: item.lgbsn, 중권역: item.mdmbsn, 환경청: item.rgnNm, - 시도: item.sidoNm, - 시군구: item.sggNm, + 시도: item.sido, + 시군구: item.gugun, 시설이름: item.bscFctNm, 주소: item.addr, "시설용량(m³/일)": CommaToNumber(item.fctCpc), @@ -527,27 +562,28 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType "유출TP(mg/L)": CommaToNumber(item.efflWqltTp), }); }); - }else{ - res.data.forEach((item: FacilityWaterQltyProps) => { + } else { + // res.data.forEach((item: FacilityWaterQltyProps) => { + waterQltyList.forEach((item: FacilityWaterQltyProps) => { dataFormSetting(item) item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; - item.inflAssctBfrWqltToc = Number(searchYear)<COD_YEAR? "("+item.inflAssctBfrWqltToc+")" : item.inflAssctBfrWqltToc; - item.efflWqltToc = Number(searchYear)<COD_YEAR? "("+item.efflWqltToc+")" : item.efflWqltToc; + // item.sidoNm = item.; + // item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; + item.inflAssctBfrWqltToc = Number(searchYear) < COD_YEAR ? "(" + item.inflAssctBfrWqltToc + ")" : item.inflAssctBfrWqltToc; + item.efflWqltToc = Number(searchYear) < COD_YEAR ? "(" + item.efflWqltToc + ")" : item.efflWqltToc; }); } set((state: FacilityWaterQltyType) => ({ ...state, - FacilityWaterQltyMain: pageSize<5000?{ + FacilityWaterQltyMain: pageSize < 5000 ? { ...state.FacilityWaterQltyMain, facilityWaterQltyList: rtnData, - facilityWaterQltyCount: res.count, - isLoading : false, - }:{ + facilityWaterQltyCount: recordCount, + isLoading: false, + } : { ...state.FacilityWaterQltyMain, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })) }, @@ -555,10 +591,10 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType searchFacilityWaterQltyListHist: async (searchYear: string, facilityCd: string) => { const res = await fetchFacilityWaterQltyListHist(searchYear, facilityCd); const res1 = await fetchFacilityWaterQltyTmsList(facilityCd); - + let rtnMsrmtDate = ''; const rtnTmsList: FacilityWaterQltyTmsProps[] = []; - if(res1.data){ + if (res1.data) { rtnTmsList.push({ part: '평균', toc: res1.data.tocAvgVl, @@ -566,7 +602,6 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType tn: res1.data.tnAvgVl, tp: res1.data.tpAvgVl, }); - rtnTmsList.push({ part: '최대', toc: res1.data.tocMaxVl, @@ -574,7 +609,6 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType tn: res1.data.tnMaxVl, tp: res1.data.tpMaxVl, }); - rtnTmsList.push({ part: '최소', toc: res1.data.tocMinVl, @@ -584,42 +618,21 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType }); rtnMsrmtDate = res1.data.msrmtDate; - }else{ - rtnTmsList.push({ - part: '평균', - toc: 0, - ss: 0, - tn: 0, - tp: 0, - }); - - rtnTmsList.push({ - part: '최대', - toc: 0, - ss: 0, - tn: 0, - tp: 0, - }); - - rtnTmsList.push({ - part: '최소', - toc: 0, - ss: 0, - tn: 0, - tp: 0, - }); - + } else { + rtnTmsList.push({ part: '평균', toc: 0, ss: 0, tn: 0, tp: 0, }); + rtnTmsList.push({ part: '최대', toc: 0, ss: 0, tn: 0, tp: 0, }); + rtnTmsList.push({ part: '최소', toc: 0, ss: 0, tn: 0, tp: 0, }); rtnMsrmtDate = ''; } - - if(Array.isArray(res) || !res || !('data' in res) || !res.data || !res.data.wqltObjs || res.data.wqltObjs.length === 0) { + + if (Array.isArray(res) || !res || !('data' in res) || !res.data || !res.data.wqltObjs || res.data.wqltObjs.length === 0) { set((state: FacilityWaterQltyType) => ({ ...state, FacilityWaterQltyModal: { ...state.FacilityWaterQltyModal, facilityWaterQltyYearList: [], - tmsList : rtnTmsList, - msrmtDate : rtnMsrmtDate + tmsList: rtnTmsList, + msrmtDate: rtnMsrmtDate } })) return; @@ -627,19 +640,17 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType // facilityPrecipitation의 rainEffectDay 계산 로직을 적용 const data = CalcRainEffect(res.data.wqltObjs); - + /** * [(강우시 일별유입유량*강우시 일별유입BOD)-(청천시 연평균유입유량*청천시 연평균유입BOD)]​ /강우시 일별유입BOD) */ - //let rainInflAmnt = 0; let sunnyInflAmnt = 0; let sunnyBod = 0; - //let rainDay = 0; let sunnyDay = 0; data.forEach(item => { - if(item.rainEffectDay == 1){ + if (item.rainEffectDay == 1) { //rainDay += 1; //rainInflAmnt += Number(item.inflAmnt); } else { @@ -650,69 +661,69 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType }); //const yearRainAmnt = rainInflAmnt===0?0:rainInflAmnt / rainDay; - const yearSunnyAmnt = sunnyInflAmnt===0?0:sunnyInflAmnt / sunnyDay; - const yearSunnyBod = sunnyBod===0?0:sunnyBod / sunnyDay; + const yearSunnyAmnt = sunnyInflAmnt === 0 ? 0 : sunnyInflAmnt / sunnyDay; + const yearSunnyBod = sunnyBod === 0 ? 0 : sunnyBod / sunnyDay; // const rtnData = data.map((item: FacilityWaterQltyProps) => { dataFormSetting(item) - if(item.inflAssctBfrWqltTn) { - item.inflAssctBfrWqltTn = Number(Math.round(Number(item.inflAssctBfrWqltTn) * 1000)/1000).toFixed(3); + if (item.inflAssctBfrWqltTn) { + item.inflAssctBfrWqltTn = Number(Math.round(Number(item.inflAssctBfrWqltTn) * 1000) / 1000).toFixed(3); } - if(item.efflWqltTn){ - item.efflWqltTn = Number(Math.round(Number(item.efflWqltTn) * 1000)/1000).toFixed(3); + if (item.efflWqltTn) { + item.efflWqltTn = Number(Math.round(Number(item.efflWqltTn) * 1000) / 1000).toFixed(3); } - if(item.inflAssctBfrWqltTp){ - item.inflAssctBfrWqltTp = Number(Math.round(Number(item.inflAssctBfrWqltTp) * 1000)/1000).toFixed(3); + if (item.inflAssctBfrWqltTp) { + item.inflAssctBfrWqltTp = Number(Math.round(Number(item.inflAssctBfrWqltTp) * 1000) / 1000).toFixed(3); } - if(item.efflWqltTp){ - item.efflWqltTp = Number(Math.round(Number(item.efflWqltTp) * 1000)/1000).toFixed(3); + if (item.efflWqltTp) { + item.efflWqltTp = Number(Math.round(Number(item.efflWqltTp) * 1000) / 1000).toFixed(3); } - if(item.inflAssctBfrWqltToc){ - item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltToc) { + item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10) / 10).toFixed(1); } - if(item.efflWqltToc){ - item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); + if (item.efflWqltToc) { + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltCod){ - item.inflAssctBfrWqltCod = "("+Number(Math.round(Number(item.inflAssctBfrWqltCod) * 10)/10).toFixed(1)+")"; + if (item.inflAssctBfrWqltCod) { + item.inflAssctBfrWqltCod = "(" + Number(Math.round(Number(item.inflAssctBfrWqltCod) * 10) / 10).toFixed(1) + ")"; } - if(item.efflWqltCod){ - item.efflWqltCod = "("+Number(Math.round(Number(item.efflWqltCod) * 10)/10).toFixed(1)+")"; + if (item.efflWqltCod) { + item.efflWqltCod = "(" + Number(Math.round(Number(item.efflWqltCod) * 10) / 10).toFixed(1) + ")"; } - if(item.inflAssctBfrWqltBod){ - item.inflAssctBfrWqltBod = Number(Math.round(Number(item.inflAssctBfrWqltBod) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltBod) { + item.inflAssctBfrWqltBod = Number(Math.round(Number(item.inflAssctBfrWqltBod) * 10) / 10).toFixed(1); } - if(item.efflWqltBod){ - item.efflWqltBod = Number(Math.round(Number(item.efflWqltBod) * 10)/10).toFixed(1); + if (item.efflWqltBod) { + item.efflWqltBod = Number(Math.round(Number(item.efflWqltBod) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltSs){ - item.inflAssctBfrWqltSs = Number(Math.round(Number(item.inflAssctBfrWqltSs) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltSs) { + item.inflAssctBfrWqltSs = Number(Math.round(Number(item.inflAssctBfrWqltSs) * 10) / 10).toFixed(1); } - if(item.efflWqltSs){ - item.efflWqltSs = Number(Math.round(Number(item.efflWqltSs) * 10)/10).toFixed(1); + if (item.efflWqltSs) { + item.efflWqltSs = Number(Math.round(Number(item.efflWqltSs) * 10) / 10).toFixed(1); } - if(item.inflAssctBfrWqltToc){ - item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10)/10).toFixed(1); + if (item.inflAssctBfrWqltToc) { + item.inflAssctBfrWqltToc = Number(Math.round(Number(item.inflAssctBfrWqltToc) * 10) / 10).toFixed(1); } - if(item.efflWqltToc){ - item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); + if (item.efflWqltToc) { + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10) / 10).toFixed(1); } - if(item.tpInflWqltBod){ - item.tpInflWqltBod = item.tpInflWqltBod?Number(Math.round(Number(item.tpInflWqltBod) * 10)/10).toFixed(1):Number(item.tpInflWqltBod)===0?'0.0':''; - }else{ + if (item.tpInflWqltBod) { // 총인 유입수질 BOD(mg/L) + item.tpInflWqltBod = item.tpInflWqltBod ? Number(Math.round(Number(item.tpInflWqltBod) * 10) / 10).toFixed(1) : Number(item.tpInflWqltBod) === 0 ? '0.0' : ''; + } else { item.tpInflWqltBod = ''; } - if(item.tpInflWqltTp){ - item.tpInflWqltTp = item.tpInflWqltTp?Number(Math.round(Number(item.tpInflWqltTp) * 1000)/1000).toFixed(3): Number(item.tpInflWqltTp)===0?'0.000':''; - }else{ + if (item.tpInflWqltTp) { // 총인 유입수질 TP(mg/L) + item.tpInflWqltTp = item.tpInflWqltTp ? Number(Math.round(Number(item.tpInflWqltTp) * 1000) / 1000).toFixed(3) : Number(item.tpInflWqltTp) === 0 ? '0.000' : ''; + } else { item.tpInflWqltTp = ''; } const dayBod = Number(item.inflAssctBfrWqltBod); const noProcessInflAmnt = roundNumber(((Number(item.inflAmnt) * dayBod) - (yearSunnyAmnt * yearSunnyBod)) / dayBod, 0) - item.noProcessInflAmnt = noProcessInflAmnt && Number(noProcessInflAmnt.replaceAll(',','')) > 0 ? noProcessInflAmnt : ''; + item.noProcessInflAmnt = noProcessInflAmnt && Number(noProcessInflAmnt.replaceAll(',', '')) > 0 ? noProcessInflAmnt : ''; return item; }) @@ -721,8 +732,8 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType FacilityWaterQltyModal: { ...state.FacilityWaterQltyModal, facilityWaterQltyYearList: rtnData, - tmsList : rtnTmsList, - msrmtDate : rtnMsrmtDate + tmsList: rtnTmsList, + msrmtDate: rtnMsrmtDate } })) }, diff --git a/apps/fo/app/store/flowRate/index.tsx b/apps/fo/app/store/flowRate/index.tsx index 66d639fb8..b097fc234 100644 --- a/apps/fo/app/store/flowRate/index.tsx +++ b/apps/fo/app/store/flowRate/index.tsx @@ -1,6 +1,6 @@ import { getFetch } from "@/app/components/api"; import type { TableUpperProps } from "../dashboard"; -import DataForm, { roundNumber } from "@/app/components/common/DataForm"; +import DataForm from "@/app/components/common/DataForm"; import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import { fetchFacilityWaterQltyListHist } from "../facilityWaterQlty"; import CommaToNumber from "@/app/components/common/CommaToNumber"; @@ -12,34 +12,34 @@ import CommaToNumber from "@/app/components/common/CommaToNumber"; /** * (1) 메인페이지 */ -const FlowRateSearchLabelArray:string[] = ['구분', '시도', '시군구', '용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; +const FlowRateSearchLabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; export type flowRateListProps = { - bscFctCd : string, - sido : string, - gugun : string, - bscFctNm : string, - daddr : string, - fctCpc : number, - lclgvrnmntNm : string, - totInflAmnt : number | '', - inflAmnt : number | '', - sumFlow : number | '', - returnAmnt : number | '', - efflAmnt : number | '', - rgnNm : string, - rvbsn : string, - lgbsn : string, - mdmbsn : string, - infoRnDay : number | '', + bscFctCd: string, + sido: string, + gugun: string, + bscFctNm: string, + daddr: string, + fctCpc: number, + lclgvrnmntNm: string, + totInflAmnt: number | '', + inflAmnt: number | '', + sumFlow: number | '', + returnAmnt: number | '', + efflAmnt: number | '', + rgnNm: string, + rvbsn: string, + lgbsn: string, + mdmbsn: string, + infoRnDay: number | '', } export interface ExcelType { 시설코드: string, - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', + 권역: string | '', + 단위유역: string | '', + 중권역: string | '', + 환경청: string | '', 시도: string, 시군구: string, 시설이름: string, @@ -66,105 +66,105 @@ export type modalExcelType = { * (2) 모달 */ export interface FlowRateModalInterface { - msrDt : string, - totInflAmnt : number | '', - inflAmnt : number | '', - sumFlow : number | '', - returnAmnt : number | '', - efflAmnt : number | '', - infoRnDay : number | string | '', - rainEffectDay : number | string | '' , - noProcessInflAmnt : number | string | '' + msrDt: string, + totInflAmnt: number | '', + inflAmnt: number | '', + sumFlow: number | '', + returnAmnt: number | '', + efflAmnt: number | '', + infoRnDay: number | string | '', + rainEffectDay: number | string | '', + noProcessInflAmnt: number | string | '' } export type FlowRateModalProps = { - msrDt : string, - totInflAmnt : number, - inflAmnt : number, - sumFlow : number, - returnAmnt : number, - efflAmnt : number, - infoRnDay : number | string | '', - rainEffectDay : number | string | '', - noProcessInflAmnt : number | string | '' + msrDt: string, + totInflAmnt: number, + inflAmnt: number, + sumFlow: number, + returnAmnt: number, + efflAmnt: number, + infoRnDay: number | string | '', + rainEffectDay: number | string | '', + noProcessInflAmnt: number | string | '' } /**==================================== * Store - data ====================================*/ - /** - * (1) 메인페이지 - */ +/** +* (1) 메인페이지 +*/ // 리스트 (행정별) -const upHeadList1:TableUpperProps[] = [ +const upHeadList1: TableUpperProps[] = [ { id: "A", title: "행정명", row: 0, colSpan: 2, widthPercent: 16 }, - { id: "sido", title: "시도", upName:"upChangeRe", row:1, widthPercent: 8 }, - { id: "gugun", title: "시군구", upName:"upChangeRe", row:1, widthPercent: 8 }, - { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10}, + { id: "sido", title: "시도", upName: "upChangeRe", row: 1, widthPercent: 8 }, + { id: "gugun", title: "시군구", upName: "upChangeRe", row: 1, widthPercent: 8 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, - { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "inflAmnt", title: "하수유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "sumFlow", title: '연계처리량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "efflAmnt", title: '방류량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 } + { id: "fctCpc", title: "시설용량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "inflAmnt", title: "하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "sumFlow", title: '연계처리량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "efflAmnt", title: '방류량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 } ]; // 리스트 (권역별) -const upHeadList2:TableUpperProps[] = [ +const upHeadList2: TableUpperProps[] = [ { id: "A", title: "유역", row: 0, colSpan: 3, widthPercent: 24 }, - { id: "rvbsn", title: "권역", upName:"upChangeRe", row:1, widthPercent: 8 }, - { id: "lgbsn", title: "단위유역", upName:"upChangeRe", row:1, widthPercent: 8 }, - { id: "mdmbsn", title: "중권역", upName:"upChangeRe", row:1, widthPercent: 8 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe", row: 1, widthPercent: 8 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe", row: 1, widthPercent: 8 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe", row: 1, widthPercent: 8 }, { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, - { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 10 }, - { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "inflAmnt", title: "하수유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "sumFlow", title: '연계처리량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "efflAmnt", title: '방류량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 } + { id: "fctCpc", title: "시설용량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "inflAmnt", title: "하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "sumFlow", title: '연계처리량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "efflAmnt", title: '방류량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 } ]; // 리스트 (환경청별별) -const upHeadList3:TableUpperProps[] = [ +const upHeadList3: TableUpperProps[] = [ { id: "A", title: "유역", row: 0, colSpan: 3, widthPercent: 15 }, - { id: "rvbsn", title: "권역", upName:"upChangeRe1", row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", upName:"upChangeRe1", row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", upName:"upChangeRe1", row:1, widthPercent: 5 }, - { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 7 }, - { id: "B", title: "행정명", row: 0 ,colSpan: 2, widthPercent: 10 }, - { id: "sido", title: "시도", upName:"upChangeRe2", row:1, widthPercent: 5 }, - { id: "gugun", title: "시군구", upName:"upChangeRe2", row:1, widthPercent: 5 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe1", row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe1", row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe1", row: 1, widthPercent: 5 }, + { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "B", title: "행정명", row: 0, colSpan: 2, widthPercent: 10 }, + { id: "sido", title: "시도", upName: "upChangeRe2", row: 1, widthPercent: 5 }, + { id: "gugun", title: "시군구", upName: "upChangeRe2", row: 1, widthPercent: 5 }, { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 7 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, - { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "inflAmnt", title: "하수유입량\n(m³/일)", align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "sumFlow", title: '연계처리량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 }, - { id: "efflAmnt", title: '방류량\n(m³/일)', align:"right", row: 0, rowSpan: 2, widthPercent: 7 } + { id: "fctCpc", title: "시설용량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "totInflAmnt", title: "총 유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "inflAmnt", title: "하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "sumFlow", title: '연계처리량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "returnAmnt", title: '총인 반류수량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 }, + { id: "efflAmnt", title: '방류량\n(m³/일)', align: "right", row: 0, rowSpan: 2, widthPercent: 7 } ]; -const flowRateList:flowRateListProps[] = [ +const flowRateList: flowRateListProps[] = [ ] /** * (2) 모달 */ -const upHeadListFlowRateModal:TableUpperProps[] = [ - { id: "msrDt", title: "날짜"}, - { id: "totInflAmnt", title: "총 유입량(m³/일)", align:"right"}, - { id: "inflAmnt", title: "하수유입량(m³/일)", align:"right"}, - { id: "sumFlow", title: "연계처리량(m³/일)", align:"right"}, - { id: "returnAmnt", title: "총인 반류수량(m³/일)", align:"right"}, - { id: "efflAmnt", title: "방류량(m³/일)", align:"right"}, - { id: "noProcessInflAmnt", title: "미처리하수 추정량(m³/일)", align:"right"}, - { id: "infoRnDay", title: "강우량(mm/일)", align:"right"} +const upHeadListFlowRateModal: TableUpperProps[] = [ + { id: "msrDt", title: "날짜" }, + { id: "totInflAmnt", title: "총 유입량(m³/일)", align: "right" }, + { id: "inflAmnt", title: "하수유입량(m³/일)", align: "right" }, + { id: "sumFlow", title: "연계처리량(m³/일)", align: "right" }, + { id: "returnAmnt", title: "총인 반류수량(m³/일)", align: "right" }, + { id: "efflAmnt", title: "방류량(m³/일)", align: "right" }, + { id: "noProcessInflAmnt", title: "미처리하수 추정량(m³/일)", align: "right" }, + { id: "infoRnDay", title: "강우량(mm/일)", align: "right" } ] -const modalFlowRateListYear:FlowRateModalProps[] = [ +const modalFlowRateListYear: FlowRateModalProps[] = [ ] /**==================================== @@ -172,13 +172,14 @@ const modalFlowRateListYear:FlowRateModalProps[] = [ ====================================*/ const fetchFlowRateList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { try { - const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; + const tailStr = partCd ? '/' + partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; const url = '/fclt/flow/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr; const res = await getFetch('fo', url); - return { data: res.data, count: res.count } + return { data: res.data, count: res.count, status: res.status} } catch (error) { console.error('시설정보 유량현황 데이터를 가져오는데 실패했습니다:', error); - return { data: [], count: 0 }; + console.log(error); + return { data: [], count: 0, status: 300 }; } } @@ -186,12 +187,12 @@ export const fetchFlowRateModalList = async (bscFctCd: string, searchYear: strin try { const url = '/fclt/flow/' + bscFctCd + '/' + searchYear; const res = await getFetch('fo', url); - + // msrDt 오름차순으로 정렬 const sortedData = res.data.sort((a: FlowRateModalInterface, b: FlowRateModalInterface) => { return new Date(a.msrDt).getTime() - new Date(b.msrDt).getTime(); }); - + return { data: sortedData } } catch (error) { console.error('시설정보 유량현황 모달 데이터를 가져오는데 실패했습니다:', error); @@ -206,69 +207,69 @@ export const fetchFlowRateModalList = async (bscFctCd: string, searchYear: strin export type FlowRateSearchActions = { searchFlowRateList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void; - - searchFlowRateModalList : (bscFctCd: string, searchYear: string) => void; + + searchFlowRateModalList: (bscFctCd: string, searchYear: string) => void; } export type FlowRateSearchType = { FlowRateSearch: { isInit: boolean; - flowRateSearchLabelArray:string[]; + flowRateSearchLabelArray: string[]; }, - FlowRateList : { - isInit : boolean; + FlowRateList: { + isInit: boolean; upHeadList1: TableUpperProps[]; upHeadList2: TableUpperProps[]; upHeadList3: TableUpperProps[]; - flowRateList:flowRateListProps[]; + flowRateList: flowRateListProps[]; flowRateCount: number; excelList: ExcelType[]; isLoading: boolean; }, - FlowRateModal : { - isInit : boolean; - modalFlowRateListYear : FlowRateModalProps[]; - upHeadListFlowRateModalYear : TableUpperProps[]; + FlowRateModal: { + isInit: boolean; + modalFlowRateListYear: FlowRateModalProps[]; + upHeadListFlowRateModalYear: TableUpperProps[]; } } export type FlowRateSearchStore = FlowRateSearchType & FlowRateSearchActions; -const dataFormSetting=(item : flowRateListProps | FlowRateModalProps)=> { +export const dataFormSetting = (item: flowRateListProps | FlowRateModalProps | FlowRateModalInterface) => { DataForm({ type: 'date', column: 'msrDt', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'totInflAmnt', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'inflAmnt', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'sumFlow', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'efflWqltBod', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'returnAmnt', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'efflAmnt', item: item as unknown as Record<string, unknown>}); + DataForm({ type: 'float0', column: 'totInflAmnt', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'inflAmnt', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'sumFlow', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'efflWqltBod', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'returnAmnt', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'efflAmnt', item: item as unknown as Record<string, unknown> }); } -export const flowRateSearchInitState:FlowRateSearchType = { +export const flowRateSearchInitState: FlowRateSearchType = { FlowRateSearch: { isInit: false, flowRateSearchLabelArray: FlowRateSearchLabelArray, }, - FlowRateList : { - isInit : false, - upHeadList1 : upHeadList1, - upHeadList2 : upHeadList2, - upHeadList3 : upHeadList3, - flowRateList : flowRateList, - flowRateCount : 0, + FlowRateList: { + isInit: false, + upHeadList1: upHeadList1, + upHeadList2: upHeadList2, + upHeadList3: upHeadList3, + flowRateList: flowRateList, + flowRateCount: 0, excelList: [], isLoading: true, }, - FlowRateModal : { - isInit : false, - upHeadListFlowRateModalYear : upHeadListFlowRateModal, - modalFlowRateListYear : modalFlowRateListYear, + FlowRateModal: { + isInit: false, + upHeadListFlowRateModalYear: upHeadListFlowRateModal, + modalFlowRateListYear: modalFlowRateListYear, } } -export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => FlowRateSearchType) => void)=>FlowRateSearchActions=(set: (fn: (state: FlowRateSearchType) => FlowRateSearchType) => void) => { +export const flowRateSearchReducer: (set: (fn: (state: FlowRateSearchType) => FlowRateSearchType) => void) => FlowRateSearchActions = (set: (fn: (state: FlowRateSearchType) => FlowRateSearchType) => void) => { return { searchFlowRateList: async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { set((state: FlowRateSearchType) => ({ @@ -278,17 +279,50 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow isLoading: true, }, })); - const res = await fetchFlowRateList(pageNo, pageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); - const data = res.data; + + // const res = await fetchFlowRateList(pageNo, pageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); + + //--------- + const flowRateList: any[] = []; + const results: any[] = []; + if (pageSize < 5000) { + const res = await fetchFlowRateList(pageNo, pageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); + flowRateList.push(...res.data); + results.push(res); + } else { + for (let idx = 0; idx < pageSize; idx += 100) { + const tmpPageNo = Math.floor(idx / 100) + 1; + const tmpPageSize = Math.min(100, pageSize - idx); + const result = await fetchFlowRateList( tmpPageNo, tmpPageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); + results.push(result); + if (result?.count < tmpPageNo * tmpPageSize) { + break; // 더 이상 데이터가 없으면 중지 + } + } + // console.log(results); + // 결과를 합치기 + + for (const res of results) { + if (res?.status === 200 && Array.isArray(res?.data)) { + flowRateList.push(...res.data); + } + } + } + //--------- + const recordCount = results[0]?.count || 0; + //------- + + // const data = res.data; + const data = flowRateList; const excelDownList: ExcelType[] = []; const rtnData = data.map((item: flowRateListProps) => { item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.totInflAmnt = Number(item.inflAmnt || 0) + Number(item.sumFlow || 0) + Number(item.returnAmnt || 0) + item.totInflAmnt = Number(item.inflAmnt || 0) + Number(item.sumFlow || 0) + Number(item.returnAmnt || 0); // 하수유입량 + 연계처리량 + 반류수유량(m³/일) dataFormSetting(item); return item; }); - if(pageSize>5000){ + if (pageSize > 5000) { rtnData.forEach((item: flowRateListProps) => { excelDownList.push({ 시설코드: item.bscFctCd, @@ -312,15 +346,15 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow set((state: FlowRateSearchType) => ({ ...state, - FlowRateList: pageSize<5000?{ + FlowRateList: pageSize < 5000 ? { ...state.FlowRateList, flowRateList: rtnData, - flowRateCount: res.count, - isLoading : false, - }:{ + flowRateCount: recordCount, + isLoading: false, + } : { ...state.FlowRateList, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })) }, @@ -329,44 +363,48 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow const inflData = CalcRainEffect(res.data); const res2 = await fetchFacilityWaterQltyListHist(searchYear, bscFctCd); - const wqltData = CalcRainEffect(res2?.data.wqltObjs); - + const wqltData = res2?.data.wqltObjs; + //const wqltData = CalcRainEffect(res2?.data.wqltObjs); /** - * [(강우시 일별유입유량*강우시 일별유입BOD)-(청천시 연평균유입유량*청천시 연평균유입BOD)]​ - /강우시 일별유입BOD) + * [(강우시 일별유입유량*강우시 일별유입BOD)-(청천시 연평균유입유량*청천시 연평균유입BOD)] / 강우시 일별유입BOD */ - //let rainInflAmnt = 0; - let sunnyInflAmnt = 0; - let sunnyBod = 0; - //let rainDay = 0; + let sunnyInflAmnt = 0; // 청천시 일별유입유량 + let sunnyBod = 0; // 청천시 일별유입BOD let sunnyDay = 0; - - inflData.forEach(item => { - if(item.rainEffectDay == 1){ - //rainDay += 1; - //rainInflAmnt += Number(item.inflAmnt); + let rainEffectDay = 0; + wqltData?.forEach(item => { + if (item.infoRnDay == null) item.infoRnDay = 0; + if (Number(item.infoRnDay) >= 3) { + rainEffectDay = 2; } else { - const msrDt = item.msrDt.replaceAll('-',''); + rainEffectDay--; + } + if (rainEffectDay < 0) { //청천시 sunnyDay += 1; sunnyInflAmnt += Number(item.inflAmnt); - sunnyBod += Number(wqltData.filter((wqitem)=>wqitem.msrDt === msrDt)[0].inflAssctBfrWqltBod); + sunnyBod += Number(item.inflAssctBfrWqltBod); } }); - //const yearRainAmnt = rainInflAmnt===0?0:rainInflAmnt / rainDay; - const yearSunnyAmnt = sunnyInflAmnt===0?0:sunnyInflAmnt / sunnyDay; - const yearSunnyBod = sunnyBod===0?0:sunnyBod / sunnyDay; - + const yearSunnyAmnt = sunnyDay > 0 ? sunnyInflAmnt / sunnyDay : 0; // 청천시 연평균유입유량 + const yearSunnyBod = sunnyDay > 0 ? sunnyBod / sunnyDay : 0; // 청천시 연평균유입BOD + //console.log(inflData); const rtnData = inflData.map((item: FlowRateModalProps) => { - const dayBod = Number(wqltData.filter(wqitem=>wqitem.msrDt === item.msrDt.replaceAll('-',''))[0].inflAssctBfrWqltBod); - const noProcessInflAmnt = roundNumber(((Number(item.inflAmnt) * dayBod) - (yearSunnyAmnt * yearSunnyBod)) / dayBod, 0) - - item.totInflAmnt = Number(item.inflAmnt || 0) + Number(item.sumFlow || 0) + Number(item.returnAmnt || 0); - item.noProcessInflAmnt = noProcessInflAmnt && Number(noProcessInflAmnt.replaceAll(',','')) > 0 ? noProcessInflAmnt : ''; - dataFormSetting(item); - return item; + const targetWqItem = wqltData?.find(wqitem => wqitem.msrDt === item.msrDt.replaceAll('-', '')); + const dayBod = targetWqItem ? Number(targetWqItem.inflAssctBfrWqltBod) : 0; + const noProcessInflAmnt = Math.round(((Number(item.inflAmnt) * dayBod) - (yearSunnyAmnt * yearSunnyBod)) / dayBod) + const totInflAmnt = Number(item.inflAmnt || 0) + Number(item.sumFlow || 0) + Number(item.returnAmnt || 0); + + return { + ...item, + totInflAmnt, + noProcessInflAmnt: noProcessInflAmnt && noProcessInflAmnt > 0 ? noProcessInflAmnt : '', + msrDt: (item.msrDt.length === 8 && !item.msrDt.includes('-')) ? + `${item.msrDt.slice(0, 4)}-${item.msrDt.slice(4, 6)}-${item.msrDt.slice(6, 8)}` + : item.msrDt, + }; }); - + //console.log(rtnData); set((state: FlowRateSearchType) => ({ ...state, FlowRateModal: { diff --git a/apps/fo/app/store/index.tsx b/apps/fo/app/store/index.tsx index bd684542b..f84bda1d1 100644 --- a/apps/fo/app/store/index.tsx +++ b/apps/fo/app/store/index.tsx @@ -7,9 +7,7 @@ import { type FoStore, StoreZus } from './store' export type StoreApi = ReturnType<typeof StoreZus> -export const StoreContext = createContext<StoreApi | undefined>( - undefined, -) +export const StoreContext = createContext<StoreApi | null>(null); export interface StoreProviderProps { children: ReactNode @@ -18,7 +16,7 @@ export interface StoreProviderProps { export const StoreProvider = ({ children, }: StoreProviderProps) => { - const storeRef = useRef<StoreApi>() + const storeRef = useRef<StoreApi | null>(null); if (!storeRef.current) { const store = StoreZus() diff --git a/apps/fo/app/store/planSewage/index.tsx b/apps/fo/app/store/planSewage/index.tsx index 44ab12440..1f81dec01 100644 --- a/apps/fo/app/store/planSewage/index.tsx +++ b/apps/fo/app/store/planSewage/index.tsx @@ -1,37 +1,34 @@ import DataForm from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; -import { getFetch } from "@/app/components/api"; -import { type ApiResponse, type FlowDataItem } from "../underWaterInput"; -import CalcRainEffect from "@/app/components/common/CalcRainEffect"; +import { getFetch, postFetch } from "@/app/components/api"; +import { type ApiResponse } from "../underWaterInput"; +// import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import { roundUp } from "../common"; +// import { fetchSweageOccurListFlow, fetchWtrUsgYmList } from "../sewageOccur"; export type { TableUpperProps }; -/**==================================== - * Store - 타입 - ====================================*/ - /** - * (1) 메인페이지 + * 처리시설 연간 운영현황 > 계획하수량 산정지표 현황 */ // 시설별 강수현황 type PlanSewageProps = { - bscFctCd : string, - sido : string, - gugun : string, - bscFctNm : string, - daddr : string, - fctCpc : number, - spareCapacity : number, - variableLoadPercent : number, - classifiedSewerPercent : number, - rgnNm : string, - rvbsn : string, - lgbsn : string, - mdmbsn : string, - sprtProcZoneAr : number, - procZoneSm : number, - avgInflAmnt : number, - avgInflAmntSunny : number, - maxInflAmntSunny : number, + bscFctCd: string, + sido: string, + gugun: string, + bscFctNm: string, + daddr: string, + fctCpc: number, + spareCapacity: number, + variableLoadPercent: number, + classifiedSewerPercent: number, + rgnNm: string, + rvbsn: string, + lgbsn: string, + mdmbsn: string, + sprtProcZoneAr: number, + procZoneSm: number, + avgInflAmnt: number, + avgInflAmntSunny: number, + maxInflAmntSunny: number, } /**==================================== @@ -40,90 +37,136 @@ type PlanSewageProps = { /** * (1) 메인페이지 - */ + */ // 검색단 -const LabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; +const LabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; // 리스트 헤더 -const upHeadList1:TableUpperProps[] = [ - { id: "A", title: "행정구역", upSequnce:1, row:0, colSpan:2 }, - { id: "sido", title: "시도", upName:"upChangeRe", upSequnce:1, row:1, widthPercent: 10 }, - { id: "gugun", title: "시군구", upName:"upChangeRe",upSequnce:2, row:1, widthPercent: 10 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 10 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "spareCapacity", title: "여유용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "variableLoadPercent", title: "변동부하율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "classifiedSewerPercent", title: "분류식화율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, +const upHeadList1: TableUpperProps[] = [ + { id: "A", title: "행정구역", upSequnce: 1, row: 0, colSpan: 2 }, + { id: "sido", title: "시도", upName: "upChangeRe", upSequnce: 1, row: 1, widthPercent: 10 }, + { id: "gugun", title: "시군구", upName: "upChangeRe", upSequnce: 2, row: 1, widthPercent: 10 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "spareCapacity", title: "여유용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "variableLoadPercent", title: "변동부하율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "classifiedSewerPercent", title: "분류식화율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, ]; -const upHeadList2:TableUpperProps[] = [ - { id: "A", title: "유역", upName:"upChangeRe", upSequnce:1, row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", upName:"upChangeRe", upSequnce:1, row:1, widthPercent: 8 }, - { id: "lgbsn", title: "단위유역", upName:"upChangeRe", upSequnce:2, row:1, widthPercent: 8}, - { id: "mdmbsn", title: "중권역", upName:"upChangeRe", upSequnce:3, row:1, widthPercent: 8}, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 10 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "spareCapacity", title: "여유용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "variableLoadPercent", title: "변동부하율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "classifiedSewerPercent", title: "분류식화율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, +const upHeadList2: TableUpperProps[] = [ + { id: "A", title: "유역", upName: "upChangeRe", upSequnce: 1, row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe", upSequnce: 1, row: 1, widthPercent: 8 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe", upSequnce: 2, row: 1, widthPercent: 8 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe", upSequnce: 3, row: 1, widthPercent: 8 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "spareCapacity", title: "여유용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "variableLoadPercent", title: "변동부하율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "classifiedSewerPercent", title: "분류식화율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, ]; -const upHeadList3:TableUpperProps[] = [ - { id: "A", title: "유역", upName:"upChangeRe", upSequnce:1, row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", upName:"upChangeRe1", upSequnce:1, row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", upName:"upChangeRe1", upSequnce:2, row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", upName:"upChangeRe1", upSequnce:3, row:1, widthPercent: 5}, - { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 8 }, - { id: "B", title: "행정구역", upName:"upChangeRe", upSequnce:1, row:0, colSpan:2 }, - { id: "sido", title: "시도", upName:"upChangeRe2", upSequnce:1, row:1, widthPercent: 5 }, - { id: "gugun", title: "시군구", upName:"upChangeRe2",upSequnce:2, row:1, widthPercent: 5 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 10 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "spareCapacity", title: "여유용량(m³/일)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "variableLoadPercent", title: "변동부하율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, - { id: "classifiedSewerPercent", title: "분류식화율(%)", row:0, rowSpan:2, align:'right', widthPercent: 8 }, +const upHeadList3: TableUpperProps[] = [ + { id: "A", title: "유역", upName: "upChangeRe", upSequnce: 1, row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe1", upSequnce: 1, row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe1", upSequnce: 2, row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe1", upSequnce: 3, row: 1, widthPercent: 5 }, + { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "B", title: "행정구역", upName: "upChangeRe", upSequnce: 1, row: 0, colSpan: 2 }, + { id: "sido", title: "시도", upName: "upChangeRe2", upSequnce: 1, row: 1, widthPercent: 5 }, + { id: "gugun", title: "시군구", upName: "upChangeRe2", upSequnce: 2, row: 1, widthPercent: 5 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "spareCapacity", title: "여유용량(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "variableLoadPercent", title: "변동부하율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, + { id: "classifiedSewerPercent", title: "분류식화율(%)", row: 0, rowSpan: 2, align: 'right', widthPercent: 8 }, ]; -const planSewageList:PlanSewageProps[] = [ +const planSewageList: PlanSewageProps[] = [ ] /**==================================== * Store - api ====================================*/ - const fetchPlanSewageList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { - const tailStr = partCd ? '/'+ partCd + '/' + (nameCd || '0') + '/' + (fcltCpct || '0') + '/' + (operationValue || '0') +'/0'+ (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; + +/** +* 계획시설정보 리스트 데이터 호출(구분 선택 후 시설명 있을 경우) +* @param pageNo : 페이지 번호 +* @param pageSize : 페이지 크기 +* @param searchYear : 검색년도 +* @param partCd : 구분코드 +* @param nameCd : 행정코드(시도명, 시군구명, 단위유역) +* @param fcltCpct : 시설용량 +* @param oprtngSttsCd : 운영 상태 코드 +* @param facilityName : 시설명 +*/ +const fetchPlanSewageList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string + , fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { + const tailStr = partCd ? '/' + partCd + '/' + (nameCd || '0') + '/' + (fcltCpct || '0') + '/' + (operationValue || '0') + '/0' + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; const url = '/prcsFclt/info/plan/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr; const res = await getFetch('fo', url); return { data: res.data, count: res.count } - } +} +// 분류식화율 구하기 위한 api 호출 : 분류식화율 = 분류식지역 면적(㎢) / 처리​구역 면적합계(㎢) × 100 +const fetchPlanZoneList = async (searchYear: string, fcltCdList: string[]) => { + try { + const res = await postFetch('fo', '/prcsFclt/info/planZone/' + searchYear, fcltCdList); + return res; + } catch (error) { + console.error('시설정보 오수발생량 상세 데이터를 가져오는데 실패했습니다:', error); + return null; + } +} + +// 시설들 유량 정보 조회 +// export const fetchFlow = async (searchYear: string | undefined, fcltCdList: string[]): Promise<ApiResponse | null> => { +// try { +// const params = new URLSearchParams(); +// fcltCdList.forEach(cd => params.append('fcltCds', cd)); +// const res = await getFetch('fo', '/fclt/flow/' + searchYear + '?' + params.toString()); +// return res; +// } catch (error) { +// console.error('시설정보 유량 데이터를 가져오는데 실패했습니다:', error); +// return null; +// } +// }; +// 시설들 유량 정보 조회 +export const fetchFlow = async (searchYear: string | undefined, fcltCdList: string[]): Promise<ApiResponse | null> => { + try { + const res = await postFetch('fo', '/fclt/flow/' + searchYear, fcltCdList); + return res; + } catch (error) { + console.error('시설정보 유량 데이터를 가져오는데 실패했습니다:', error); + return null; + } +}; /**==================================== * Store - expaort ====================================*/ export type ExcelType = { 시설코드: string; - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', + 권역: string | '', + 단위유역: string | '', + 중권역: string | '', + 환경청: string | '', 시도: string; 시군구: string; 시설이름: string; 주소: string; "시설용량(m³/일)": number; "여유용량(m³/일)": number; - "변동부하율(%)" : number; - "분류식화율(%)" : number; + "변동부하율(%)": number; + "분류식화율(%)": number; } -const dataFormSetting=(item : PlanSewageProps)=> { +const dataFormSetting = (item: PlanSewageProps) => { DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'spareCapacity', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float2', column: 'variableLoadPercent', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float1', column: 'classifiedSewerPercent', item: item as unknown as Record<string, unknown>}); + DataForm({ type: 'float0', column: 'spareCapacity', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float2', column: 'variableLoadPercent', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float1', column: 'classifiedSewerPercent', item: item as unknown as Record<string, unknown> }); }; export type PlanSewageActions = { @@ -133,10 +176,10 @@ export type PlanSewageActions = { export type PlanSewageType = { PlanSewageSearch: { isInit: boolean; - labelArray:string[]; + labelArray: string[]; }, - PlanSewage : { + PlanSewage: { isInit: boolean; upHeadList1: TableUpperProps[]; upHeadList2: TableUpperProps[]; @@ -154,21 +197,21 @@ export const planSewageInitState: PlanSewageType = { labelArray: LabelArray, }, - PlanSewage : { - isInit : false, - upHeadList1 : upHeadList1, - upHeadList2 : upHeadList2, - upHeadList3 : upHeadList3, - planSewageList : planSewageList, - planSewageCount : 0, - excelList : [], - isLoading : true, + PlanSewage: { + isInit: false, + upHeadList1: upHeadList1, + upHeadList2: upHeadList2, + upHeadList3: upHeadList3, + planSewageList: planSewageList, + planSewageCount: 0, + excelList: [], + isLoading: true, }, } export type PlanSewageStore = PlanSewageType & PlanSewageActions; -export const planSewageReducer:(set:(fn: (state: PlanSewageType) => PlanSewageType) => void)=>PlanSewageActions=(set: (fn: (state: PlanSewageType) => PlanSewageType) => void) => { +export const planSewageReducer: (set: (fn: (state: PlanSewageType) => PlanSewageType) => void) => PlanSewageActions = (set: (fn: (state: PlanSewageType) => PlanSewageType) => void) => { return { searchPlanSewageList: async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { set((state: PlanSewageType) => ({ @@ -179,48 +222,152 @@ export const planSewageReducer:(set:(fn: (state: PlanSewageType) => PlanSewageTy }, })); const res = await fetchPlanSewageList(pageNo, pageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); - const excelDownList: ExcelType[] = []; - const data = res.data - const processedData = await Promise.all(data.map(async (item:PlanSewageProps) => { - const res2 = await getFetch('fo', '/fclt/flow/' + item.bscFctCd + '/' + searchYear) as ApiResponse; - const calData = CalcRainEffect(res2.data, 'SUNNY'); + const fcltylist = res.data; // 시설목록 + const fcltCdList: string[] = []; // 시설코드목록 + const planSewageList: PlanSewageProps[] = []; // 오수발생량시설목록 + fcltylist.forEach(item => { + fcltCdList.push(item.bscFctCd); + planSewageList.push({ + bscFctCd: item.bscFctCd, + sido: item.sido, + gugun: item.gugun, + bscFctNm: item.bscFctNm, + daddr: item.daddr, + fctCpc: parseFloat(item.fctCpc.toString().replace(/,/g, '')), + spareCapacity: 0, + variableLoadPercent: 0, + classifiedSewerPercent: 0, + rgnNm: item.rgnNm, //환경청 + rvbsn: item.rvbsn, //단위유역 + lgbsn: item.lgbsn, //권역 + mdmbsn: item.mdmbsn, //중권역 + sprtProcZoneAr: 0, // 분류식지역 면적(㎢) + procZoneSm: 0, // 처리구역 면적합계(㎢) + avgInflAmnt: 0, + avgInflAmntSunny: 0, + maxInflAmntSunny: 0, + }); + }); + + const excelDownList: ExcelType[] = []; + // const data = res.data + + // 분류식화율 구하기 위한 api 호출 : 분류식화율 = 분류식지역 면적(㎢) / 처리​구역 면적합계(㎢) × 100 + // const planZoneRes: any = await fetchPlanZoneList(searchYear, fcltCdList); + // const detailRes: any = await fetchFlow(searchYear, fcltCdList); + //--------- + const planZoneList: any[] = []; + const flowDetailList: Record<string, any[]> = {}; + // const results: any[] = []; + const flowDetailResults: any[] = []; + for (let idx = 0; idx < fcltCdList.length; idx += 100) { + const slicedItems = fcltCdList.slice(idx, idx + 100); + const result = await fetchPlanZoneList(searchYear, slicedItems); // 분류식화율 구하기 위한 api 호출 : 분류식화율 = 분류식지역 면적(㎢) / 처리​구역 면적합계(㎢) × 100 + const flowResult = await fetchFlow(searchYear, slicedItems); + if (result?.status === 200 && Array.isArray(result?.data)) { + planZoneList.push(...result.data); + } + flowDetailResults.push(flowResult); + } + //console.log(results); + // 결과를 합치기 + for (const res of flowDetailResults) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + if (!flowDetailList[keyStr]) flowDetailList[keyStr] = []; + flowDetailList[keyStr].push(...(value as any[])); + } + } + } + //------- + + + // const planZoneList = planZoneRes?.data; + planZoneList?.forEach(item => { + const planSewageItem = planSewageList.find(item2 => item2.bscFctCd == item.bscFctCd); + if (planSewageItem) { + planSewageItem.sprtProcZoneAr = item.sprtProcZoneAr; // 처리구역면적_분류식 + planSewageItem.procZoneSm = item.procZoneSm; //처리구역면적합계 + } + }); + + //const detailRes: any = await fetchFlow(searchYear, fcltCdList); + for (const item of planSewageList) { + // const detailList = detailRes?.data[item.bscFctCd]; + const detailList = flowDetailList[item.bscFctCd]; + + let avgInflAmnt = 0; let sumInflAmntSunny = 0; let avgInflAmntSunny = 0; let maxInflAmntSunny = 0; - calData.forEach((flowItem:FlowDataItem) => { - sumInflAmntSunny += Number(flowItem.inflAmnt); + let cntSunny = 0; + let rainEffectDay = 0; + detailList?.forEach(item2 => { - if(Number(flowItem.inflAmnt) > maxInflAmntSunny){ - maxInflAmntSunny = Number(flowItem.inflAmnt); + if (item2.infoRnDay == null || item2.infoRnDay == 'x') item2.infoRnDay = 0; + if (item2.infoRnDay >= 3) { // 강우량이 3이상이면 그 후 2일이 강후영향일임. + rainEffectDay = 2; + } else { + rainEffectDay--; } - }); + if (rainEffectDay < 0) { //청천시 + sumInflAmntSunny += Number(item2.inflAmnt); + cntSunny++; - avgInflAmntSunny = sumInflAmntSunny / calData.length; + if (Number(item2.inflAmnt) > maxInflAmntSunny) { + maxInflAmntSunny = Number(item2.inflAmnt); + } + } + avgInflAmnt += Number(item2.inflAmnt); + }); + avgInflAmntSunny = sumInflAmntSunny / cntSunny; + item.avgInflAmnt = roundUp(avgInflAmnt, 0); item.avgInflAmntSunny = roundUp(avgInflAmntSunny, 0); item.maxInflAmntSunny = roundUp(maxInflAmntSunny, 0); - return item; - })); - + } + + // const processedData = await Promise.all(data.map(async (item: PlanSewageProps) => { + // const res2 = await getFetch('fo', '/fclt/flow/' + item.bscFctCd + '/' + searchYear) as ApiResponse; + // const calData = CalcRainEffect(res2.data, 'SUNNY'); + + // let sumInflAmntSunny = 0; // 청천시 유입량 합계 + // let avgInflAmntSunny = 0; // 청천시 유입량 평균 + // let maxInflAmntSunny = 0; // 청천시 유입량 최대값 + // calData.forEach((flowItem: FlowDataItem) => { + // sumInflAmntSunny += Number(flowItem.inflAmnt); + + // if (Number(flowItem.inflAmnt) > maxInflAmntSunny) { + // maxInflAmntSunny = Number(flowItem.inflAmnt); + // } + // }); + + // avgInflAmntSunny = sumInflAmntSunny / calData.length; + // item.avgInflAmntSunny = roundUp(avgInflAmntSunny, 0); + // item.maxInflAmntSunny = roundUp(maxInflAmntSunny, 0); + // return item; + // })); + /** * 결과데이터 계산 * (1) 여유용량 = 시설용량 - 건기시 평균유량 * (2) 변동부하율 = 건기시 일최대 유량/일평균유량(기준 최근 3개년) * (3) 분류식화율 = 분류식지역 면적(㎢)/처리구역 면적합계(㎢) ×100 */ - const resultData = processedData.map((item:PlanSewageProps) => { + const resultData = planSewageList.map((item: PlanSewageProps) => { item.spareCapacity = Number(item.fctCpc) - Number(item.avgInflAmntSunny); - item.variableLoadPercent = item.maxInflAmntSunny && item.maxInflAmntSunny!==0 ? Number(item.maxInflAmntSunny) / Number(item.avgInflAmntSunny) : 0; - item.classifiedSewerPercent = item.sprtProcZoneAr && item.sprtProcZoneAr!==0 ? Number(item.sprtProcZoneAr) / Number(item.procZoneSm) * 100 : 0; + item.variableLoadPercent = item.maxInflAmntSunny && item.maxInflAmntSunny !== 0 ? Number(item.maxInflAmntSunny) / Number(item.avgInflAmntSunny) : 0; + item.classifiedSewerPercent = item.sprtProcZoneAr && item.sprtProcZoneAr !== 0 ? Number(item.sprtProcZoneAr) / Number(item.procZoneSm) * 100 : 0; // 분류식화율 return item; }); - resultData.forEach((item:PlanSewageProps) => { + resultData.forEach((item: PlanSewageProps) => { dataFormSetting(item) }) - if(pageSize>5000){ - resultData.forEach((item:PlanSewageProps) => { + if (pageSize > 5000) { + resultData.forEach((item: PlanSewageProps) => { excelDownList.push({ 시설코드: item.bscFctCd, 권역: item.lgbsn, @@ -238,18 +385,18 @@ export const planSewageReducer:(set:(fn: (state: PlanSewageType) => PlanSewageTy }) }) } - - set((state:PlanSewageType) => ({ + + set((state: PlanSewageType) => ({ ...state, - PlanSewage: pageSize<5000?{ + PlanSewage: pageSize < 5000 ? { ...state.PlanSewage, planSewageList: resultData, planSewageCount: res.count, - isLoading : false, - }:{ + isLoading: false, + } : { ...state.PlanSewage, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })) } diff --git a/apps/fo/app/store/processArea/index.tsx b/apps/fo/app/store/processArea/index.tsx index 959b83794..31da21bfc 100644 --- a/apps/fo/app/store/processArea/index.tsx +++ b/apps/fo/app/store/processArea/index.tsx @@ -12,52 +12,52 @@ export type { TableUpperProps }; */ // 처리구역 리스트 export type ProcessAreaProps = { - swtrmtFcltCd : string, + swtrmtFcltCd: string, ctpvNm?: string, sggNm?: string, - swtrmtFcltNm : string, - location : string, - fctCpc : number | '', - fcltCpctSum : number | '', - ruse : boolean, - planYrDt4 : string, - planYr : string, - planYrDc1 : string, - planYrDc2 : string, - planYrDc3 : string, - planYrDc4 : string, + swtrmtFcltNm: string, + location: string, + fctCpc: number | '', + fcltCpctSum: number | '', + ruse: boolean, + planYrDt4: string, + planYr: string, + planYrDc1: string, + planYrDc2: string, + planYrDc3: string, + planYrDc4: string, /** * 증설계획 */ // 기승인(당초4단계), 현재, 변경1~4 - planSwageQntSumDt4 : number | '', - planSwageQntSum : number | '', - planSwageQntSumDc1 : number | '', - planSwageQntSumDc2 : number | '', - planSwageQntSumDc3 : number | '', - planSwageQntSumDc4 : number | '', + planSwageQntSumDt4: number | '', + planSwageQntSum: number | '', + planSwageQntSumDc1: number | '', + planSwageQntSumDc2: number | '', + planSwageQntSumDc3: number | '', + planSwageQntSumDc4: number | '', /** * 처리인구 */ // 기승인(당초4단계), 변경1~4 - swtrmtPpltnCntDt4 : number | '', - swtrmtPpltnCnt : number | '', - swtrmtPpltnCntDc1 : number | '', - swtrmtPpltnCntDc2 : number | '', - swtrmtPpltnCntDc3 : number | '', - swtrmtPpltnCntDc4 : number | '', + swtrmtPpltnCntDt4: number | '', + swtrmtPpltnCnt: number | '', + swtrmtPpltnCntDc1: number | '', + swtrmtPpltnCntDc2: number | '', + swtrmtPpltnCntDc3: number | '', + swtrmtPpltnCntDc4: number | '', /** * 처리구역면적 */ // 기승인(당초4단계), 변경1~4 - swtrmtZoneAreaDt4 : number | '', - swtrmtZoneArea : number | '', - swtrmtZoneAreaDc1 : number | '', - swtrmtZoneAreaDc2 : number | '', - swtrmtZoneAreaDc3 : number | '', - swtrmtZoneAreaDc4 : number | '', + swtrmtZoneAreaDt4: number | '', + swtrmtZoneArea: number | '', + swtrmtZoneAreaDc1: number | '', + swtrmtZoneAreaDc2: number | '', + swtrmtZoneAreaDc3: number | '', + swtrmtZoneAreaDc4: number | '', // 처리구역 - trtDstr : string | '', + trtDstr: string | '', } export interface ExcelType { @@ -89,32 +89,32 @@ export interface ExcelType { // (2) 처리구역 팝업 // 시설증설 계획 export type FcltyImproveInfoProps = { - planSwageQntSumDt4 : number | '', - planSwageQntSum : number | '', - planSwageQntSumDc1 : number | '', - planSwageQntSumDc2 : number | '', - planSwageQntSumDc3 : number | '', - planSwageQntSumDc4 : number | '', + planSwageQntSumDt4: number | '', + planSwageQntSum: number | '', + planSwageQntSumDc1: number | '', + planSwageQntSumDc2: number | '', + planSwageQntSumDc3: number | '', + planSwageQntSumDc4: number | '', } // 처리인구 계획 export type ProcessPopulationProps = { - swtrmtPpltnCntDt4 : number | '', - swtrmtPpltnCnt : number | '', - swtrmtPpltnCntDc1 : number | '', - swtrmtPpltnCntDc2 : number | '', - swtrmtPpltnCntDc3 : number | '', - swtrmtPpltnCntDc4 : number | '', + swtrmtPpltnCntDt4: number | '', + swtrmtPpltnCnt: number | '', + swtrmtPpltnCntDc1: number | '', + swtrmtPpltnCntDc2: number | '', + swtrmtPpltnCntDc3: number | '', + swtrmtPpltnCntDc4: number | '', } // 처리구역 계획 export type ProcessAreaPlanProps = { - swtrmtZoneAreaDt4 : number | '', - swtrmtZoneArea : number | '', - swtrmtZoneAreaDc1 : number | '', - swtrmtZoneAreaDc2 : number | '', - swtrmtZoneAreaDc3 : number | '', - swtrmtZoneAreaDc4 : number | '', - trtDstr : string, + swtrmtZoneAreaDt4: number | '', + swtrmtZoneArea: number | '', + swtrmtZoneAreaDc1: number | '', + swtrmtZoneAreaDc2: number | '', + swtrmtZoneAreaDc3: number | '', + swtrmtZoneAreaDc4: number | '', + trtDstr: string, } /**==================================== @@ -123,93 +123,93 @@ export type ProcessAreaPlanProps = { /** * (1) 메인페이지 - */ + */ // 검색단 -const ProcessFacilityLabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '시설상태', '연도별', '권역', '단위유역', '중권역', '환경청별']; +const ProcessFacilityLabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '시설상태', '연도별', '권역', '단위유역', '중권역', '환경청별']; // 리스트 헤더(행정별) -const upHeadList1:TableUpperProps[] = [ - { id: "A", title: "행정구역", colSpan:2, rowSpan:2, row:0 }, - { id: "ctpvNm", title: "시도", upName:"upChangeRe", row:2, widthPercent:4 }, - { id: "sggNm", title: "시군구", upName:"upChangeRe", row:2, widthPercent:4 }, - { id: "swtrmtFcltNm", title: "시설명", rowSpan:3, row:0, widthPercent: 6 }, - { id: "location", title: "주소", rowSpan:3, row:0, widthPercent: 12 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", rowSpan:3, row:0, widthPercent: 5 }, - { id: "B", title: "시설계획", colSpan:19, row: 0 }, - { id: "C", title: "증설계획(m³/일)", colSpan:6, row: 1 }, - { id: "planSwageQntSumDt4", title: "기승인\n4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "planSwageQntSum", title: "현재", row:2, align:"right", widthPercent: 3.5 }, - { id: "planSwageQntSumDc1", title: "1단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "planSwageQntSumDc2", title: "2단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "planSwageQntSumDc3", title: "3단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "planSwageQntSumDc4", title: "4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "D", title: "처리인구(인)", colSpan:6, row:1 }, - { id: "swtrmtPpltnCntDt4", title: "기승인\n4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtPpltnCnt", title: "현재", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtPpltnCntDc1", title: "1단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtPpltnCntDc2", title: "2단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtPpltnCntDc3", title: "3단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtPpltnCntDc4", title: "4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "E", title: "처리구역(km²)", colSpan:7, row:1 }, - { id: "swtrmtZoneAreaDt4", title: "기승인\n4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtZoneArea", title: "현재", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtZoneAreaDc1", title: "1단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtZoneAreaDc2", title: "2단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtZoneAreaDc3", title: "3단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "swtrmtZoneAreaDc4", title: "4단계", row:2, align:"right", widthPercent: 3.5 }, - { id: "trtDstr", title: "처리구역", row:2} +const upHeadList1: TableUpperProps[] = [ + { id: "A", title: "행정구역", colSpan: 2, rowSpan: 2, row: 0 }, + { id: "ctpvNm", title: "시도", upName: "upChangeRe", row: 2, widthPercent: 4 }, + { id: "sggNm", title: "시군구", upName: "upChangeRe", row: 2, widthPercent: 4 }, + { id: "swtrmtFcltNm", title: "시설명", rowSpan: 3, row: 0, widthPercent: 6 }, + { id: "location", title: "주소", rowSpan: 3, row: 0, widthPercent: 12 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", rowSpan: 3, row: 0, widthPercent: 5 }, + { id: "B", title: "시설계획", colSpan: 18, row: 0 }, + { id: "C", title: "증설계획(m³/일)", colSpan: 6, row: 1 }, + { id: "planSwageQntSumDt4", title: "기승인\n4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "planSwageQntSum", title: "현재", row: 2, align: "right", widthPercent: 3.5 }, + { id: "planSwageQntSumDc1", title: "1단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "planSwageQntSumDc2", title: "2단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "planSwageQntSumDc3", title: "3단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "planSwageQntSumDc4", title: "4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "D", title: "처리인구(인)", colSpan: 6, row: 1 }, + { id: "swtrmtPpltnCntDt4", title: "기승인\n4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtPpltnCnt", title: "현재", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtPpltnCntDc1", title: "1단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtPpltnCntDc2", title: "2단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtPpltnCntDc3", title: "3단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtPpltnCntDc4", title: "4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "E", title: "처리구역(km²)", colSpan: 6, row: 1 }, + { id: "swtrmtZoneAreaDt4", title: "기승인\n4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtZoneArea", title: "현재", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtZoneAreaDc1", title: "1단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtZoneAreaDc2", title: "2단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtZoneAreaDc3", title: "3단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "swtrmtZoneAreaDc4", title: "4단계", row: 2, align: "right", widthPercent: 3.5 }, + { id: "trtDstr", title: "처리구역", row: 0, rowSpan: 3 } // row - header에서 몇번째 행인지 ]; -const processAreaList:ProcessAreaProps[] = [ +const processAreaList: ProcessAreaProps[] = [ ] /** * 처리구역정보 상세팝업 - */ + */ // 시설증설 계획 (헤더) -const upHeadListFcltyImproveInfo:TableUpperProps[] = [ - { id: "planSwageQntSumDt4", title: "기승인\n4단계(m³/일)", rowSpan:2, row:0, align:"right"}, - { id: "A", title: "증설계획(m³/일)", colSpan:5, row:0}, - { id: "planSwageQntSum", title: "현재", row:1, align:"right"}, - { id: "planSwageQntSumDc1", title: "1단계", row:1, align:"right"}, - { id: "planSwageQntSumDc2", title: "2단계", row:1, align:"right"}, - { id: "planSwageQntSumDc3", title: "3단계", row:1, align:"right"}, - { id: "planSwageQntSumDc4", title: "4단계", row:1, align:"right"}, +const upHeadListFcltyImproveInfo: TableUpperProps[] = [ + { id: "planSwageQntSumDt4", title: "기승인\n4단계(m³/일)", rowSpan: 2, row: 0, align: "right" }, + { id: "A", title: "증설계획(m³/일)", colSpan: 5, row: 0 }, + { id: "planSwageQntSum", title: "현재", row: 1, align: "right" }, + { id: "planSwageQntSumDc1", title: "1단계", row: 1, align: "right" }, + { id: "planSwageQntSumDc2", title: "2단계", row: 1, align: "right" }, + { id: "planSwageQntSumDc3", title: "3단계", row: 1, align: "right" }, + { id: "planSwageQntSumDc4", title: "4단계", row: 1, align: "right" }, ]; // 시설증설 계획 (리스트) -const gridListFcltyImproveInfo:FcltyImproveInfoProps[] = [ +const gridListFcltyImproveInfo: FcltyImproveInfoProps[] = [ ]; // 처리인구 계획 (헤더) -const upHeadListProcessPopulation:TableUpperProps[] = [ - { id: "swtrmtPpltnCntDt4", title: "기승인\n4단계(인)", rowSpan:2, row:0, align:"right"}, - { id: "A", title: "처리인구(인)", colSpan:5, row:0}, - { id: "swtrmtPpltnCnt", title: "현재", row:1, align:"right"}, - { id: "swtrmtPpltnCntDc1", title: "1단계", row:1, align:"right"}, - { id: "swtrmtPpltnCntDc2", title: "2단계", row:1, align:"right"}, - { id: "swtrmtPpltnCntDc3", title: "3단계", row:1, align:"right"}, - { id: "swtrmtPpltnCntDc4", title: "4단계", row:1, align:"right"}, +const upHeadListProcessPopulation: TableUpperProps[] = [ + { id: "swtrmtPpltnCntDt4", title: "기승인\n4단계(인)", rowSpan: 2, row: 0, align: "right" }, + { id: "A", title: "처리인구(인)", colSpan: 5, row: 0 }, + { id: "swtrmtPpltnCnt", title: "현재", row: 1, align: "right" }, + { id: "swtrmtPpltnCntDc1", title: "1단계", row: 1, align: "right" }, + { id: "swtrmtPpltnCntDc2", title: "2단계", row: 1, align: "right" }, + { id: "swtrmtPpltnCntDc3", title: "3단계", row: 1, align: "right" }, + { id: "swtrmtPpltnCntDc4", title: "4단계", row: 1, align: "right" }, ]; // 처리인구 계획 (리스트) -const gridListProcessPopulation:ProcessPopulationProps[] = [ +const gridListProcessPopulation: ProcessPopulationProps[] = [ ]; // 처리구역 계획 (헤더) -const upHeadListProcessAreaPlan:TableUpperProps[] = [ - { id: "swtrmtZoneAreaDt4", title: "기승인\n4단계(km²)", rowSpan:2, row:0, align:"right"}, - { id: "A", title: "처리면적(km²)", colSpan:6, row:0, align:"right"}, - { id: "swtrmtZoneArea", title: "현재", row:1, align:"right"}, - { id: "swtrmtZoneAreaDc1", title: "1단계", row:1, align:"right"}, - { id: "swtrmtZoneAreaDc2", title: "2단계", row:1, align:"right"}, - { id: "swtrmtZoneAreaDc3", title: "3단계", row:1, align:"right"}, - { id: "swtrmtZoneAreaDc4", title: "4단계", row:1, align:"right"}, - { id: "trtDstr", title: "처리구역", row:1} +const upHeadListProcessAreaPlan: TableUpperProps[] = [ + { id: "swtrmtZoneAreaDt4", title: "기승인\n4단계(km²)", rowSpan: 2, row: 0, align: "right" }, + { id: "A", title: "처리면적(km²)", colSpan: 5, row: 0, align: "right" }, + { id: "swtrmtZoneArea", title: "현재", row: 1, align: "right" }, + { id: "swtrmtZoneAreaDc1", title: "1단계", row: 1, align: "right" }, + { id: "swtrmtZoneAreaDc2", title: "2단계", row: 1, align: "right" }, + { id: "swtrmtZoneAreaDc3", title: "3단계", row: 1, align: "right" }, + { id: "swtrmtZoneAreaDc4", title: "4단계", row: 1, align: "right" }, + // { id: "trtDstr", title: "처리구역", row: 1 } ]; // 시설증설 계획 (리스트) -const gridListProcessAreaPlan:ProcessAreaPlanProps[] = [ +const gridListProcessAreaPlan: ProcessAreaPlanProps[] = [ ]; /**==================================== @@ -227,113 +227,115 @@ const gridListProcessAreaPlan:ProcessAreaPlanProps[] = [ } }; */ -const fetchFacilityDetailInfo = async (pageNo:number, pageSize:number, swtrmtFcltCd: string[]) : Promise<{data:any[], count:number}> => { - try { - const res = await postFetch('fo', '/prcsFclt/ruseInfo/'+pageNo+'/'+pageSize, swtrmtFcltCd); - return {data: res.data, count:res.count}; - } catch (error) { - console.error('시설정보 데이터를 가져오는데 실패했습니다:', error); - return {data:[], count:0}; - } +const fetchFacilityDetailInfo = async (pageNo: number, pageSize: number, swtrmtFcltCd: string[]): Promise<{ data: any[], count: number }> => { + try { + const res = await postFetch('fo', '/prcsFclt/ruseInfo/' + pageNo + '/' + pageSize, swtrmtFcltCd); + return { data: res.data, count: res.count }; + } catch (error) { + console.error('시설정보 데이터를 가져오는데 실패했습니다:', error); + return { data: [], count: 0 }; + } }; // 시설정보 리스트 데이터 호출(구분 선택 후 시설명 있을 경우) -const fetchProcessAreaList = async (pageNo : number, pageSize : number, ctpvNm : string | undefined, sggNm : string | undefined, targetYear : string | undefined, set : (fn: (state: ProcessAreaType) => ProcessAreaType) => void): Promise<{data:any[], count:number}> => { - try { - const res = await fetchPlanInfo(targetYear, ctpvNm, sggNm, undefined); - if(res && res.length > 0){ - const enrichedData = await Promise.all( - res.map(async (item: ProcessAreaProps) => { - upHeadList1.forEach((head:TableUpperProps) => { - /* if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { - head.title = '현재\n(' + item.planYr + '년)'; - } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { - head.title = '1단계\n(' + item.planYrDc1 + '년)'; - } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { - head.title = '2단계\n(' + item.planYrDc2 + '년)'; - } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { - head.title = '3단계\n(' + item.planYrDc3 + '년)'; - } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { - head.title = '4단계\n(' + item.planYrDc4 + '년)'; - } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { - head.title = '기승인\n4단계(' + item.planYrDt4 + '년)'; - } */ - if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { - head.title = '현재'; - } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { - head.title = '1단계'; - } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { - head.title = '2단계'; - } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { - head.title = '3단계'; - } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { - head.title = '4단계'; - } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { - head.title = '기승인\n4단계'; - } - }); - return item; +const fetchProcessAreaList = async (pageNo: number, pageSize: number, ctpvNm: string | undefined, sggNm: string | undefined + , targetYear: string | undefined, set: (fn: (state: ProcessAreaType) => ProcessAreaType) => void): Promise<{ data: any[], count: number }> => { + try { + const res = await fetchPlanInfo(targetYear, ctpvNm, sggNm, undefined); + if (res && res.length > 0) { + const enrichedData = await Promise.all( + res.map(async (item: ProcessAreaProps) => { + upHeadList1.forEach((head: TableUpperProps) => { + /* if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { + head.title = '현재\n(' + item.planYr + '년)'; + } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { + head.title = '1단계\n(' + item.planYrDc1 + '년)'; + } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { + head.title = '2단계\n(' + item.planYrDc2 + '년)'; + } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { + head.title = '3단계\n(' + item.planYrDc3 + '년)'; + } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { + head.title = '4단계\n(' + item.planYrDc4 + '년)'; + } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { + head.title = '기승인\n4단계(' + item.planYrDt4 + '년)'; + } */ + if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { + head.title = '현재'; + } else if (head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { + head.title = '1단계'; + } else if (head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { + head.title = '2단계'; + } else if (head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { + head.title = '3단계'; + } else if (head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { + head.title = '4단계'; + } else if (head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { + head.title = '기승인\n4단계'; + } + }); + return item; + }) + ); + + const startIndex = (pageNo - 1) * pageSize; + const endIndex = startIndex + pageSize; + const paginatedData = enrichedData.slice(startIndex, endIndex); + + const fcltyCdList: string[] = []; + paginatedData.forEach(item => { + fcltyCdList.push(item.swtrmtFcltCd); }) - ); - const startIndex = (pageNo - 1) * pageSize; - const endIndex = startIndex + pageSize; - const paginatedData = enrichedData.slice(startIndex, endIndex); - - const fcltyCdList : string[]= []; - paginatedData.forEach(item => { - fcltyCdList.push(item.swtrmtFcltCd); - }) - - const detailFcltylist = await fetchFacilityDetailInfo(1, pageSize, fcltyCdList); - paginatedData.forEach(item => { - const filterdData = detailFcltylist.data.filter(fItem=>fItem.bscFctCd === item.swtrmtFcltCd)[0] || null; - item.fcltCpctSum = filterdData ? filterdData.fctCpc : ''; - item.location = filterdData ? filterdData.addr : ''; - item.ctpvNm = filterdData ? filterdData.sido : ''; - item.sggNm = filterdData ? filterdData.gugun : ''; - }); - return {data: paginatedData, count: enrichedData.length}; - }else{ - return {data: [], count: 0}; - } - } catch (error) { - console.error('시설계획정보를 가져오는데 실패했습니다:', error); - set((state) => ({ - ...state, - ProcessArea : { - ...state.ProcessArea, - isLoading : false + const detailFcltylist = await fetchFacilityDetailInfo(1, pageSize, fcltyCdList); + paginatedData.forEach(item => { + const filterdData = detailFcltylist.data.filter(fItem => fItem.bscFctCd === item.swtrmtFcltCd)[0] || null; + item.fcltCpctSum = filterdData ? filterdData.fctCpc : ''; + item.location = filterdData ? filterdData.addr : ''; + item.ctpvNm = filterdData ? filterdData.sido : ''; + item.sggNm = filterdData ? filterdData.gugun : ''; + item.trtDstr = filterdData ? filterdData.trtDstr : ''; + }); + return { data: paginatedData, count: enrichedData.length }; + } else { + return { data: [], count: 0 }; } - })); - return {data: [], count: 0}; - } + } catch (error) { + console.error('시설계획정보를 가져오는데 실패했습니다:', error); + set((state) => ({ + ...state, + ProcessArea: { + ...state.ProcessArea, + isLoading: false + } + })); + return { data: [], count: 0 }; + } }; /**==================================== * Store - expaort =====================================*/ export type ProcessAreaActions = { - searchProcessAreaList : (pageNo: number, pageSize: number, ctpvNm?:string, sggNm?:string, targetYear?:string) => void; - initModalData : (swtrmtFcltCd:string) => void; + searchProcessAreaList: (pageNo: number, pageSize: number, ctpvNm?: string, sggNm?: string, targetYear?: string) => void; + initModalData: (swtrmtFcltCd: string) => void; } export type ProcessAreaType = { ProcessAreaSearch: { isInit: boolean; - processFacilityLabelArray:string[]; + processFacilityLabelArray: string[]; }, - ProcessArea : { + ProcessArea: { isInit: boolean; upHeadList1: TableUpperProps[]; processAreaList: ProcessAreaProps[]; processAreaCount: number; excelList: ExcelType[]; isLoading: boolean; - }, + }, - ProcessAreaDetail : { + ProcessAreaDetail: { isInit: boolean; upHeadListFcltyImproveInfo: TableUpperProps[]; gridListFcltyImproveInfo: FcltyImproveInfoProps[]; // Changed from facilityAreaProps to ProcessAreaProps @@ -350,23 +352,23 @@ export const processAreaInitState: ProcessAreaType = { processFacilityLabelArray: ProcessFacilityLabelArray, }, - ProcessArea : { - isInit : false, - upHeadList1 : upHeadList1, - processAreaList : processAreaList, - processAreaCount : 0, - excelList : [], - isLoading : true, + ProcessArea: { + isInit: false, + upHeadList1: upHeadList1, + processAreaList: processAreaList, + processAreaCount: 0, + excelList: [], + isLoading: true, }, - ProcessAreaDetail : { - isInit : false, - upHeadListFcltyImproveInfo : upHeadListFcltyImproveInfo, - gridListFcltyImproveInfo : gridListFcltyImproveInfo, - upHeadListProcessPopulation : upHeadListProcessPopulation, - gridListProcessPopulation : gridListProcessPopulation, - upHeadListProcessAreaPlan : upHeadListProcessAreaPlan, - gridListProcessAreaPlan : gridListProcessAreaPlan, + ProcessAreaDetail: { + isInit: false, + upHeadListFcltyImproveInfo: upHeadListFcltyImproveInfo, + gridListFcltyImproveInfo: gridListFcltyImproveInfo, + upHeadListProcessPopulation: upHeadListProcessPopulation, + gridListProcessPopulation: gridListProcessPopulation, + upHeadListProcessAreaPlan: upHeadListProcessAreaPlan, + gridListProcessAreaPlan: gridListProcessAreaPlan, }, } @@ -375,14 +377,14 @@ type SetStateFunction = (state: ProcessAreaType | ((state: ProcessAreaType) => P export type ProcessAreaStore = ProcessAreaType & ProcessAreaActions; -export const processAreaReducer:(set:SetStateFunction)=>ProcessAreaActions=(set: SetStateFunction) => { +export const processAreaReducer: (set: SetStateFunction) => ProcessAreaActions = (set: SetStateFunction) => { return { /** * 시설정보 리스트 데이터 호출 * @param pageNo : 페이지 번호 * @param pageSize : 페이지 크기 */ - searchProcessAreaList: async ( pageNo:number, pageSize:number, ctpvNm?:string, sggNm?:string, targetYear?:string) => { + searchProcessAreaList: async (pageNo: number, pageSize: number, ctpvNm?: string, sggNm?: string, targetYear?: string) => { set((state: ProcessAreaType) => ({ ...state, ProcessArea: { @@ -391,12 +393,12 @@ export const processAreaReducer:(set:SetStateFunction)=>ProcessAreaActions=(set: }, })); const res = await fetchProcessAreaList(pageNo, pageSize, ctpvNm, sggNm, targetYear, set); - res.data.forEach((item:ProcessAreaProps) => { + res.data.forEach((item: ProcessAreaProps) => { item.fctCpc = item.fcltCpctSum; - DataForm({ type: 'float1', column: 'fctCpc', item:item as unknown as Record<string, unknown>}); + DataForm({ type: 'float1', column: 'fctCpc', item: item as unknown as Record<string, unknown> }); }) const excelDownList: ExcelType[] = []; - if(pageSize>5000){ + if (pageSize > 5000) { res.data.forEach(item => { item.fctCpc = item.fctCpc ? parseFloat(item.fctCpc.toString().replace(/,/g, '')) : ''; excelDownList.push({ @@ -425,78 +427,78 @@ export const processAreaReducer:(set:SetStateFunction)=>ProcessAreaActions=(set: 처리구역: item.trtDstr }); }); - }else{ + } else { res.data.forEach(item => { item.fctCpc = item.fctCpc ? parseFloat(item.fctCpc.toString().replace(/,/g, '')) : ''; }) } - set((state:ProcessAreaType) => ({ + set((state: ProcessAreaType) => ({ ...state, - ProcessArea: pageSize<5000?{ + ProcessArea: pageSize < 5000 ? { ...state.ProcessArea, processAreaList: res.data, processAreaCount: res.count, - isLoading : false, - }:{ + isLoading: false, + } : { ...state.ProcessArea, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })) }, - initModalData: async (swtrmtFcltCd:string) => { + initModalData: async (swtrmtFcltCd: string) => { set((state: ProcessAreaType) => { const rtnData = state.ProcessArea.processAreaList.find((item: ProcessAreaProps) => item.swtrmtFcltCd === swtrmtFcltCd); if (rtnData) { - upHeadListFcltyImproveInfo.forEach((head:TableUpperProps) => { - if(head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { - head.title = rtnData.planYr? '현재\n(' + rtnData.planYr + '년)':head.title = '현재'; - } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { - head.title = rtnData.planYrDc1? '1단계\n(' + rtnData.planYrDc1 + '년)':head.title = '1단계'; - } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { - head.title = rtnData.planYrDc2? '2단계\n(' + rtnData.planYrDc2 + '년)':head.title = '2단계'; - } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { - head.title = rtnData.planYrDc3? '3단계\n(' + rtnData.planYrDc3 + '년)':head.title = '3단계'; - } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { - head.title = rtnData.planYrDc4? '4단계\n(' + rtnData.planYrDc4 + '년)':head.title = '4단계'; - } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { - head.title = rtnData.planYrDt4? '기승인\n4단계(' + rtnData.planYrDt4 + '년)':head.title = '기승인\n4단계'; + upHeadListFcltyImproveInfo.forEach((head: TableUpperProps) => { + if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { + head.title = rtnData.planYr ? '현재\n(' + rtnData.planYr + '년)' : head.title = '현재'; + } else if (head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { + head.title = rtnData.planYrDc1 ? '1단계\n(' + rtnData.planYrDc1 + '년)' : head.title = '1단계'; + } else if (head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { + head.title = rtnData.planYrDc2 ? '2단계\n(' + rtnData.planYrDc2 + '년)' : head.title = '2단계'; + } else if (head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { + head.title = rtnData.planYrDc3 ? '3단계\n(' + rtnData.planYrDc3 + '년)' : head.title = '3단계'; + } else if (head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { + head.title = rtnData.planYrDc4 ? '4단계\n(' + rtnData.planYrDc4 + '년)' : head.title = '4단계'; + } else if (head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { + head.title = rtnData.planYrDt4 ? '기승인\n4단계(' + rtnData.planYrDt4 + '년)' : head.title = '기승인\n4단계'; } }); - upHeadListProcessAreaPlan.forEach((head:TableUpperProps) => { - if(head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { - head.title = rtnData.planYr? '현재\n(' + rtnData.planYr + '년)':head.title = '현재'; - } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { - head.title = rtnData.planYrDc1?'1단계\n(' + rtnData.planYrDc1 + '년)':head.title = '1단계'; - } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { - head.title = rtnData.planYrDc2?'2단계\n(' + rtnData.planYrDc2 + '년)':head.title = '2단계'; - } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { - head.title = rtnData.planYrDc3? '3단계\n(' + rtnData.planYrDc3 + '년)':head.title = '3단계'; - } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { - head.title = rtnData.planYrDc4?'4단계\n(' + rtnData.planYrDc4 + '년)':head.title = '4단계'; - } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { - head.title = rtnData.planYrDt4?'기승인\n4단계(' + rtnData.planYrDt4 + '년)':head.title = '기승인\n4단계'; + upHeadListProcessAreaPlan.forEach((head: TableUpperProps) => { + if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { + head.title = rtnData.planYr ? '현재\n(' + rtnData.planYr + '년)' : head.title = '현재'; + } else if (head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { + head.title = rtnData.planYrDc1 ? '1단계\n(' + rtnData.planYrDc1 + '년)' : head.title = '1단계'; + } else if (head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { + head.title = rtnData.planYrDc2 ? '2단계\n(' + rtnData.planYrDc2 + '년)' : head.title = '2단계'; + } else if (head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { + head.title = rtnData.planYrDc3 ? '3단계\n(' + rtnData.planYrDc3 + '년)' : head.title = '3단계'; + } else if (head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { + head.title = rtnData.planYrDc4 ? '4단계\n(' + rtnData.planYrDc4 + '년)' : head.title = '4단계'; + } else if (head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { + head.title = rtnData.planYrDt4 ? '기승인\n4단계(' + rtnData.planYrDt4 + '년)' : head.title = '기승인\n4단계'; } }); - upHeadListProcessPopulation.forEach((head:TableUpperProps) => { - if(head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { - head.title = rtnData.planYr?'현재\n(' + rtnData.planYr + '년)':head.title = '현재'; - } else if(head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { - head.title = rtnData.planYrDc1?'1단계\n(' + rtnData.planYrDc1 + '년)':head.title = '1단계'; - } else if(head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { - head.title = rtnData.planYrDc2?'2단계\n(' + rtnData.planYrDc2 + '년)':head.title = '2단계'; - } else if(head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { - head.title = rtnData.planYrDc3?'3단계\n(' + rtnData.planYrDc3 + '년)':head.title = '3단계'; - } else if(head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { - head.title = rtnData.planYrDc4?'4단계\n(' + rtnData.planYrDc4 + '년)':head.title = '4단계'; - } else if(head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { - head.title = rtnData.planYrDt4?'기승인\n4단계(' + rtnData.planYrDt4 + '년)':head.title = '기승인\n4단계'; + upHeadListProcessPopulation.forEach((head: TableUpperProps) => { + if (head.id === 'planSwageQntSum' || head.id === 'swtrmtPpltnCnt' || head.id === 'swtrmtZoneArea') { + head.title = rtnData.planYr ? '현재\n(' + rtnData.planYr + '년)' : head.title = '현재'; + } else if (head.id === 'planSwageQntSumDc1' || head.id === 'swtrmtPpltnCntDc1' || head.id === 'swtrmtZoneAreaDc1') { + head.title = rtnData.planYrDc1 ? '1단계\n(' + rtnData.planYrDc1 + '년)' : head.title = '1단계'; + } else if (head.id === 'planSwageQntSumDc2' || head.id === 'swtrmtPpltnCntDc2' || head.id === 'swtrmtZoneAreaDc2') { + head.title = rtnData.planYrDc2 ? '2단계\n(' + rtnData.planYrDc2 + '년)' : head.title = '2단계'; + } else if (head.id === 'planSwageQntSumDc3' || head.id === 'swtrmtPpltnCntDc3' || head.id === 'swtrmtZoneAreaDc3') { + head.title = rtnData.planYrDc3 ? '3단계\n(' + rtnData.planYrDc3 + '년)' : head.title = '3단계'; + } else if (head.id === 'planSwageQntSumDc4' || head.id === 'swtrmtPpltnCntDc4' || head.id === 'swtrmtZoneAreaDc4') { + head.title = rtnData.planYrDc4 ? '4단계\n(' + rtnData.planYrDc4 + '년)' : head.title = '4단계'; + } else if (head.id === 'planSwageQntSumDt4' || head.id === 'swtrmtPpltnCntDt4' || head.id === 'swtrmtZoneAreaDt4') { + head.title = rtnData.planYrDt4 ? '기승인\n4단계(' + rtnData.planYrDt4 + '년)' : head.title = '기승인\n4단계'; } }); - + // 시설증설 계획 데이터 생성 const fcltyImproveInfo: FcltyImproveInfoProps = { planSwageQntSumDt4: rtnData.planSwageQntSumDt4, diff --git a/apps/fo/app/store/processFacility/index.tsx b/apps/fo/app/store/processFacility/index.tsx index f4184865d..006185e6d 100644 --- a/apps/fo/app/store/processFacility/index.tsx +++ b/apps/fo/app/store/processFacility/index.tsx @@ -2,152 +2,150 @@ import { getFetch } from "@/app/components/api"; import DataForm, { roundNumber } from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; export type { TableUpperProps }; - -/**==================================== - * Store - 타입 - ====================================*/ - /** - * (1) 메인페이지 + * 처리시설 현황 > 처리시설 시설정보 */ // 처리시설 리스트 export type facilityListProps = { - bscFctCd : string, - bscFctNm : string, - bscFctClsNm : string, - sido : string, - gugun : string, - daddr : string, - fctCpc : number | string | '', - plInflWqltBod : number | string | '', - dsInflWqltBod : number | string | '', - trtFrfNm : string, - rnDt : string, - cmplDt : string, - trstOprEtpNm : string, - rvbsn : string, - mdmbsn : string, - lgbsn : string, - rgnNm : string, - mnstr : string, - efflWtrrgn : string, - efflWqltStndrdNm : string + bscFctCd: string, + bscFctNm: string, + bscFctClsNm: string, + sido: string, + gugun: string, + daddr: string, + fctCpc: number | string | '', + plInflWqltBod: number | string | '', + dsInflWqltBod: number | string | '', + trtFrfNm: string, + rnDt: string, + cmplDt: string, + trstOprEtpNm: string, + rvbsn: string, + mdmbsn: string, + lgbsn: string, + rgnNm: string, + mnstr: string, + efflWtrrgn: string, + efflWqltStndrdNm: string } export interface ExcelType { - 시설코드: string, - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', - 시도: string, - 시군구: string, - 시설이름: string, - 주소: string, - "시설용량(m³/일)": number | '', - "계획유입수질(mg/L)": number | '', - "설계유입수질(mg/L)": number | '', - 공법: string, - 가동개시일: string, - 준공일: string, - 관리대행업자: string, + 시설코드: string, + 권역: string | '', + 단위유역: string | '', + 중권역: string | '', + 환경청: string | '', + 시도: string, + 시군구: string, + 시설이름: string, + 주소: string, + "시설용량(m³/일)": number | '', + "계획유입수질(mg/L)": number | '', + "설계유입수질(mg/L)": number | '', + 공법: string, + 가동개시일: string, + 준공일: string, + 관리대행업자: string, } // (2) 처리시설 팝업 // 시설 기본정보 export type BasicInfoProps = { - bscFctNm : string, - fctCpc : number | '', - sido : string, - gugun : string, - startDay : string, - addr : string, - cmplDt : string, - trtFrfNm : string, - trstOprEtpNm : string, + bscFctNm: string, + fctCpc: number | '', + sido: string, + gugun: string, + startDay: string, + addr: string, + cmplDt: string, + trtFrfNm: string, + trstOprEtpNm: string, } // 시설 계획정보 export type PlanInfoProps = { - plInflWqltBod? : string, - plInflWqltToc? : string, - plInflWqltCod? : string, - plInflWqltSs? : string, - plInflWqltTn? : string, - plInflWqltTp? : string, - dsInflWqltBod? : string, - dsInflWqltToc? : string, - dsInflWqltCod? : string, - dsInflWqltSs? : string, - dsInflWqltTn? : string, - dsInflWqltTp? : string, - sitArea? : string | number, - trtDstrArea? : string | number, - swrTrtPopl? : string | number + plInflWqltBod?: string, + plInflWqltToc?: string, + plInflWqltCod?: string, + plInflWqltSs?: string, + plInflWqltTn?: string, + plInflWqltTp?: string, + dsInflWqltBod?: string, + dsInflWqltToc?: string, + dsInflWqltCod?: string, + dsInflWqltSs?: string, + dsInflWqltTn?: string, + dsInflWqltTp?: string, + sitArea?: string | number, + trtDstrArea?: string | number, + swrTrtPopl?: string | number } // 시설 계획정보 export type LegalWaterQltyProps = { - totClfgrpcnt : number, - bod : number, - cod : number, - ss : number, - tn : number, - tp : number + totClfgrpcnt: number, + bod: number, + cod: number, + ss: number, + tn: number, + tp: number } // 시설 수역 및 구역정보 export type fcltyAreaInfoProps = { - rvbsn : string, - efflWtrrgn : string, - lgbsn : string, - efflWqltStndrdNm : string, - rgnNm : string, + rvbsn: string, + efflWtrrgn: string, + lgbsn: string, + efflWqltStndrdNm: string, + rgnNm: string, } // 시설 이력관리 export type fcltyHistInfoProps = { - extnsSeqno : string, - extnsNm : string, - bzStrtYrMnth : string, - bzEdYrMnth : string, - eqpPrmsDt : string, - cmplDt : string, - trlrnCmplDt : string, - rnDt : string, - sitArea : number, - trtDstrArea : number, - swrTrtPopl : number, - fctCpc : number | '' + extnsSeqno: string, + extnsNm: string, + bzStrtYrMnth: string, + bzEdYrMnth: string, + eqpPrmsDt: string, + cmplDt: string, + trlrnCmplDt: string, + rnDt: string, + sitArea: number, + trtDstrArea: number, + swrTrtPopl: number, + fctCpc: number | '', + trtSrsHanNm?: string, // 공법구분 + trtMocHanNm?: string, // 공법계열 + trtFrfHanNm?: string, // 방법 } // 시설 수질정보 export type fcltyWaterQltyInfoProps = { - extnsSeqno : string, - plInflWqltBod : string, - plInflWqltToc : string, - plInflWqltCod : string, - plInflWqltSs : string, - plInflWqltTn : string, - plInflWqltTp : string, - dsInflWqltBod : string, - dsInflWqltToc : string, - dsInflWqltCod : string, - dsInflWqltSs : string, - dsInflWqltTn : string, - dsInflWqltTp : string, - plEfflWqltBod : string, - plEfflWqltToc : string, - plEfflWqltCod : string, - plEfflWqltSs : string, - plEfflWqltTn : string, - plEfflWqltTp : string, - bod : number, - cod : number, - ss : number, - tn : number, - tp : number, - totClfgrpcnt : number, + extnsSeqno: string, + plInflWqltBod: string, + plInflWqltToc: string, + plInflWqltCod: string, + plInflWqltSs: string, + plInflWqltTn: string, + plInflWqltTp: string, + dsInflWqltBod: string, + dsInflWqltToc: string, + dsInflWqltCod: string, + dsInflWqltSs: string, + dsInflWqltTn: string, + dsInflWqltTp: string, + plEfflWqltBod: string, + plEfflWqltToc: string, + plEfflWqltCod: string, + plEfflWqltSs: string, + plEfflWqltTn: string, + plEfflWqltTp: string, + bod: number, + cod: number, + ss: number, + tn: number, + tp: number, + totClfgrpcnt: number, } /**==================================== @@ -156,48 +154,48 @@ export type fcltyWaterQltyInfoProps = { /** * (1) 메인페이지 - */ + */ // 검색단 -const ProcessFacilityLabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '시설상태', '시설구분', '권역', '단위유역', '중권역', '환경청별']; +const ProcessFacilityLabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '시설상태', '시설구분', '권역', '단위유역', '중권역', '환경청별']; // 리스트 헤더 -const upHeadList1:TableUpperProps[] = [ +const upHeadList1: TableUpperProps[] = [ { id: "A", title: "행정명", colSpan: 2, row: 0, widthPercent: 10 }, - { id: "sido", title: "시도", upName:"upChangeRe", row: 1, upSequnce:1, widthPercent: 5 }, - { id: "gugun", title: "시군구", upName:"upChangeRe", row: 1, upSequnce:2, widthPercent: 5 }, + { id: "sido", title: "시도", upName: "upChangeRe", row: 1, upSequnce: 1, widthPercent: 5 }, + { id: "gugun", title: "시군구", upName: "upChangeRe", row: 1, upSequnce: 2, widthPercent: 5 }, { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, { id: "fctCpc", title: "시설용량\n(m³/일)", row: 0, align: 'right', rowSpan: 2, widthPercent: 5 }, - { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", row: 0, align:"right", rowSpan: 2, widthPercent: 5 }, - { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", row: 0, align:"right", rowSpan: 2, widthPercent: 5 }, + { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", row: 0, align: "right", rowSpan: 2, widthPercent: 5 }, + { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", row: 0, align: "right", rowSpan: 2, widthPercent: 5 }, { id: "trtFrfNm", title: '공법', row: 0, rowSpan: 2 }, { id: "rnDt", title: '가동개시일', row: 0, rowSpan: 2, widthPercent: 5 }, { id: "cmplDt", title: '준공일', row: 0, rowSpan: 2, widthPercent: 5 }, - { id: "trstOprEtpNm", title: '관리대행업자', row: 0, rowSpan: 2 , widthPercent: 14} + { id: "trstOprEtpNm", title: '관리대행업자', row: 0, rowSpan: 2, widthPercent: 14 } ]; // 처리시설 리스트 (유역) -const upHeadList2:TableUpperProps[] = [ +const upHeadList2: TableUpperProps[] = [ { id: "A", title: "유역", colSpan: 3, row: 0, widthPercent: 15 }, { id: "rvbsn", title: "권역", row: 1, widthPercent: 5 }, { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 5 }, { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 5 }, - { id: "bscFctNm", title: "시설명", rowSpan: 2,row: 0, widthPercent: 8 }, - { id: "daddr", title: "주소", rowSpan: 2,row: 0 }, - { id: "fctCpc", title: "시설용량\n(m³/일)", rowSpan: 2,row: 0, widthPercent: 5, align: 'right'}, - { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", rowSpan: 2,row: 0, widthPercent: 6, align:'right'}, - { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", rowSpan: 2,row: 0, widthPercent: 6, align:'right'}, - { id: "trtFrfNm", title: '공법', rowSpan: 2,row: 0 }, - { id: "rnDt", title: '가동개시일', rowSpan: 2,row: 0, widthPercent: 5 }, - { id: "cmplDt", title: '준공일', rowSpan: 2,row: 0, widthPercent: 5 }, - { id: "trstOprEtpNm", title: '관리대행업자', rowSpan: 2,row: 0, widthPercent: 14 } + { id: "bscFctNm", title: "시설명", rowSpan: 2, row: 0, widthPercent: 8 }, + { id: "daddr", title: "주소", rowSpan: 2, row: 0 }, + { id: "fctCpc", title: "시설용량\n(m³/일)", rowSpan: 2, row: 0, widthPercent: 5, align: 'right' }, + { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", rowSpan: 2, row: 0, widthPercent: 6, align: 'right' }, + { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", rowSpan: 2, row: 0, widthPercent: 6, align: 'right' }, + { id: "trtFrfNm", title: '공법', rowSpan: 2, row: 0 }, + { id: "rnDt", title: '가동개시일', rowSpan: 2, row: 0, widthPercent: 5 }, + { id: "cmplDt", title: '준공일', rowSpan: 2, row: 0, widthPercent: 5 }, + { id: "trstOprEtpNm", title: '관리대행업자', rowSpan: 2, row: 0, widthPercent: 14 } ]; // 처리시설 리스트 (환경청청) -const upHeadList3:TableUpperProps[] = [ +const upHeadList3: TableUpperProps[] = [ { id: "A", title: "유역", colSpan: 3, row: 0, widthPercent: 12 }, { id: "rvbsn", title: "권역", row: 1, widthPercent: 4 }, - { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 4}, + { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 4 }, { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 4 }, { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 6 }, { id: "B", title: "행정명", colSpan: 2, row: 0, widthPercent: 8 }, @@ -206,178 +204,184 @@ const upHeadList3:TableUpperProps[] = [ { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, { id: "fctCpc", title: "시설용량\n(m³/일)", row: 0, rowSpan: 2, align: 'right', widthPercent: 5 }, - { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", row: 0, rowSpan: 2, align:'right', widthPercent: 5 }, - { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", row: 0, rowSpan: 2, align:'right', widthPercent: 5 }, + { id: "plInflWqltBod", title: "계획유입수질\n(mg/L)", row: 0, rowSpan: 2, align: 'right', widthPercent: 5 }, + { id: "dsInflWqltBod", title: "설계유입수질\n(mg/L)", row: 0, rowSpan: 2, align: 'right', widthPercent: 5 }, { id: "trtFrfNm", title: '공법', row: 0, rowSpan: 2 }, { id: "rnDt", title: '가동개시일', row: 0, rowSpan: 2, widthPercent: 5 }, { id: "cmplDt", title: '준공일', row: 0, rowSpan: 2, widthPercent: 5 }, { id: "trstOprEtpNm", title: '관리대행업자', row: 0, rowSpan: 2, widthPercent: 14 } ]; -const processFacilityList:facilityListProps[] = [ +const processFacilityList: facilityListProps[] = [ ] /** * 처리시설 상세팝업 - */ + */ // 시설 기본정보 (헤더) -const upHeadListBasicInfo:TableUpperProps[] = [ +const upHeadListBasicInfo: TableUpperProps[] = [ { id: "bscFctNm", title: "시설명" }, - { id: "fctCpc", title: "시설용량n(m³/일)", align: 'right'}, - { id: "sido", title: "시도"}, - { id: "gugun", title: "시군구"}, - { id: "rnDt", title: "가동개시일"}, - { id: "daddr", title: "주소"}, - { id: "cmplDt", title: "준공일"}, - { id: "trtFrfNm", title: "공법"}, - { id: "trstOprEtpNm", title: "관리대행업자"}, + { id: "fctCpc", title: "시설용량n(m³/일)", align: 'right' }, + { id: "sido", title: "시도" }, + { id: "gugun", title: "시군구" }, + { id: "rnDt", title: "가동개시일" }, + { id: "daddr", title: "주소" }, + { id: "cmplDt", title: "준공일" }, + { id: "trtFrfNm", title: "공법" }, + { id: "trstOprEtpNm", title: "관리대행업자" }, ]; // 시설 기본정보 (리스트) -const gridListBasicInfo:BasicInfoProps[] = [ +const gridListBasicInfo: BasicInfoProps[] = [ { - bscFctNm : "난지", - fctCpc : 860000, - sido : "서울특별시", - gugun : "", - startDay : "1987-06-30", - addr : "경기도 고양시 덕양구 대덕로 426 (현천동)", - cmplDt : "2013-04-30", - trtFrfNm : "MLE, A2O", - trstOprEtpNm : "자체", + bscFctNm: "난지", + fctCpc: 860000, + sido: "서울특별시", + gugun: "", + startDay: "1987-06-30", + addr: "경기도 고양시 덕양구 대덕로 426 (현천동)", + cmplDt: "2013-04-30", + trtFrfNm: "MLE, A2O", + trstOprEtpNm: "자체", } ]; // 시설 법적방류수질 (헤더) -const upHeadListLegalWaterQlty:TableUpperProps[] = [ - { id: "bod", title: "BOD(mg/L)", align: 'right'}, - { id: "cod", title: "TOC(COD)(mg/L)", align: 'right'}, - { id: "ss", title: "SS(mg/L)", align: 'right'}, - { id: "tn", title: "T-N(mg/L)", align: 'right'}, - { id: "tp", title: "T-P(mg/L) ", align: 'right'}, - { id: "totClfgrpcnt", title: "대장균군(개/100ml)"}, +const upHeadListLegalWaterQlty: TableUpperProps[] = [ + { id: "bod", title: "BOD(mg/L)", align: 'right' }, + { id: "cod", title: "TOC(COD)(mg/L)", align: 'right' }, + { id: "ss", title: "SS(mg/L)", align: 'right' }, + { id: "tn", title: "T-N(mg/L)", align: 'right' }, + { id: "tp", title: "T-P(mg/L) ", align: 'right' }, + { id: "totClfgrpcnt", title: "대장균군(개/100ml)" }, ] // 시설 법적방류수질 (리스트) -let gridListLegalWaterQlty:LegalWaterQltyProps[] = [ +let gridListLegalWaterQlty: LegalWaterQltyProps[] = [ ] // 시설 계획정보 (헤더) -const upHeadListPlanInfo:TableUpperProps[] = [ - { id: "plInflWqltBod", title: "BOD", upName:"upChangeRe1", upSequnce:1, align: 'right' }, - { id: "plInflWqltToc", title: "TOC", upName:"upChangeRe1", upSequnce:2, align: 'right' }, - { id: "plInflWqltCod", title: "COD", upName:"upChangeRe1", upSequnce:3, align: 'right' }, - { id: "plInflWqltSs", title: "SS", upName:"upChangeRe1", upSequnce:4, align: 'right' }, - { id: "plInflWqltTn", title: "T-N", upName:"upChangeRe1", upSequnce:5, align: 'right' }, - { id: "plInflWqltTp", title: "T-P", upName:"upChangeRe1", upSequnce:6, align: 'right' }, - { id: "dsInflWqltBod", title: "BOD", upName:"upChangeRe2", upSequnce:1, align: 'right' }, - { id: "dsInflWqltToc", title: "TOC", upName:"upChangeRe2", upSequnce:2, align: 'right' }, - { id: "dsInflWqltCod", title: "COD", upName:"upChangeRe2", upSequnce:3, align: 'right' }, - { id: "dsInflWqltSs", title: "SS", upName:"upChangeRe2", upSequnce:4, align: 'right' }, - { id: "dsInflWqltTn", title: "T-N", upName:"upChangeRe2", upSequnce:5, align: 'right' }, - { id: "dsInflWqltTp", title: "T-P", upName:"upChangeRe2", upSequnce:6, align: 'right' }, - { id: "sitArea", title: "부지면적\n(m²)", align: 'right'}, - { id: "trtDstrArea", title: "처리구역면적\n(ha)", align: 'right'}, - { id: "swrTrtPopl", title: "하수처리인구\n(인)", align: 'right'}, +const upHeadListPlanInfo: TableUpperProps[] = [ + { id: "plInflWqltBod", title: "BOD", upName: "upChangeRe1", upSequnce: 1, align: 'right' }, + { id: "plInflWqltToc", title: "TOC", upName: "upChangeRe1", upSequnce: 2, align: 'right' }, + { id: "plInflWqltCod", title: "COD", upName: "upChangeRe1", upSequnce: 3, align: 'right' }, + { id: "plInflWqltSs", title: "SS", upName: "upChangeRe1", upSequnce: 4, align: 'right' }, + { id: "plInflWqltTn", title: "T-N", upName: "upChangeRe1", upSequnce: 5, align: 'right' }, + { id: "plInflWqltTp", title: "T-P", upName: "upChangeRe1", upSequnce: 6, align: 'right' }, + { id: "dsInflWqltBod", title: "BOD", upName: "upChangeRe2", upSequnce: 1, align: 'right' }, + { id: "dsInflWqltToc", title: "TOC", upName: "upChangeRe2", upSequnce: 2, align: 'right' }, + { id: "dsInflWqltCod", title: "COD", upName: "upChangeRe2", upSequnce: 3, align: 'right' }, + { id: "dsInflWqltSs", title: "SS", upName: "upChangeRe2", upSequnce: 4, align: 'right' }, + { id: "dsInflWqltTn", title: "T-N", upName: "upChangeRe2", upSequnce: 5, align: 'right' }, + { id: "dsInflWqltTp", title: "T-P", upName: "upChangeRe2", upSequnce: 6, align: 'right' }, + { id: "sitArea", title: "부지면적\n(m²)", align: 'right' }, + { id: "trtDstrArea", title: "처리구역면적\n(ha)", align: 'right' }, + { id: "swrTrtPopl", title: "하수처리인구\n(인)", align: 'right' }, ]; // 시설 계획정보 (리스트) -const gridListPlanInfo:PlanInfoProps[] = [ +const gridListPlanInfo: PlanInfoProps[] = [ { - plInflWqltBod : "179.0", - plInflWqltToc : "90.0", - plInflWqltCod : "0.0", - plInflWqltSs : "132.0", - plInflWqltTn : "38.000", - plInflWqltTp : "4.300", - dsInflWqltBod : "178.0", - dsInflWqltToc : "91.0", - dsInflWqltCod : "0.0", - dsInflWqltSs : "131.0", - dsInflWqltTn : "40.200", - dsInflWqltTp : "4.500", - sitArea : "928,574", - trtDstrArea : "7,994.0", - swrTrtPopl : "1,573,277", + plInflWqltBod: "179.0", + plInflWqltToc: "90.0", + plInflWqltCod: "0.0", + plInflWqltSs: "132.0", + plInflWqltTn: "38.000", + plInflWqltTp: "4.300", + dsInflWqltBod: "178.0", + dsInflWqltToc: "91.0", + dsInflWqltCod: "0.0", + dsInflWqltSs: "131.0", + dsInflWqltTn: "40.200", + dsInflWqltTp: "4.500", + sitArea: "928,574", + trtDstrArea: "7,994.0", + swrTrtPopl: "1,573,277", } ]; // 시설 수역 및 구역정보 (헤더) -const upHeadListFcltyAreaInfo:TableUpperProps[] = [ +const upHeadListFcltyAreaInfo: TableUpperProps[] = [ { id: "rvbsn", title: "하천명" }, - { id: "efflWtrrgn", title: "방류수역"}, - { id: "lgbsn", title: "단위유역"}, - { id: "efflWqltStndrdNm", title: "지역구분"}, - { id: "rgnNm", title: "관할 유역(지방) 환경청"} + { id: "efflWtrrgn", title: "방류수역" }, + { id: "lgbsn", title: "단위유역" }, + { id: "efflWqltStndrdNm", title: "지역구분" }, + { id: "rgnNm", title: "관할 유역(지방) 환경청" } ]; // 시설 수역 및 구역정보 (리스트) -const gridListFcltyAreaInfo:fcltyAreaInfoProps[] = [ +const gridListFcltyAreaInfo: fcltyAreaInfoProps[] = [ { - rvbsn : "한강", - efflWtrrgn : "한강수계", - lgbsn : "한강본류", - efflWqltStndrdNm : "500톤 이상\n(Ⅲ지역)", - rgnNm : "한강유역환경청" + rvbsn: "한강", + efflWtrrgn: "한강수계", + lgbsn: "한강본류", + efflWqltStndrdNm: "500톤 이상\n(Ⅲ지역)", + rgnNm: "한강유역환경청" } ]; // 시설 이력관리 (헤더) -const upHeadListFcltyHistInfo:TableUpperProps[] = [ +const upHeadListFcltyHistInfo: TableUpperProps[] = [ { id: "extnsSeqno", title: "차수", align: 'center' }, - { id: "extnsNm", title: "구분"}, - { id: "bzStrtYrMnth", title: "사업기간", upName:"upChangeRe1", upSequnce:1}, - { id: "bzEdYrMnth", title: "사업기간", upName:"upChangeRe1", upSequnce:2}, - { id: "eqpPrmsDt", title: "설치인가일"}, - { id: "cmplDt", title: "준공일"}, - { id: "trlrnCmplDt", title: "시운전완료일"}, - { id: "rnDt", title: "가동개시일"}, + { id: "extnsNm", title: "구분" }, + { id: "bzStrtYrMnth", title: "사업기간", upName: "upChangeRe1", upSequnce: 1 }, + { id: "bzEdYrMnth", title: "사업기간", upName: "upChangeRe1", upSequnce: 2 }, + { id: "eqpPrmsDt", title: "설치인가일" }, + { id: "cmplDt", title: "준공일" }, + { id: "trlrnCmplDt", title: "시운전완료일" }, + { id: "rnDt", title: "가동개시일" }, { id: "fctCpc", title: "시설용량(m³/일)", align: 'right' }, + { id: "trtSrsHanNm", title: "공법구분" }, + { id: "trtMocHanNm", title: "공법계열" }, + { id: "trtFrfHanNm", title: "방법" }, ]; // 시설 이력관리 (리스트) -const gridListFcltyHistInfo:fcltyHistInfoProps[] = [ +const gridListFcltyHistInfo: fcltyHistInfoProps[] = [ { - extnsSeqno : "최초", - extnsNm : "최초", - bzStrtYrMnth : "1984.12.01", - bzEdYrMnth : "1987.06.01", - eqpPrmsDt : "1985.09.13", - cmplDt : "1987.06.30", - trlrnCmplDt : "1987.06.30", - rnDt : "1987.06.30", - sitArea : 0, - trtDstrArea : 0, - swrTrtPopl : 0, - fctCpc : 860000 + extnsSeqno: "최초", + extnsNm: "최초", + bzStrtYrMnth: "1984.12.01", + bzEdYrMnth: "1987.06.01", + eqpPrmsDt: "1985.09.13", + cmplDt: "1987.06.30", + trlrnCmplDt: "1987.06.30", + rnDt: "1987.06.30", + sitArea: 0, + trtDstrArea: 0, + swrTrtPopl: 0, + fctCpc: 860000, + trtFrfHanNm: "", + trtMocHanNm: "", + trtSrsHanNm: "", } ]; // 시설 수질정보 (헤더) -const upHeadListFcltyWaterQltyInfo:TableUpperProps[] = [ +const upHeadListFcltyWaterQltyInfo: TableUpperProps[] = [ { id: "extnsSeqno", title: "차수" }, - { id: "plInflWqltBod", title: "BOD", upName:"upChangeRe1", upSequnce:1, align: 'right' }, - { id: "plInflWqltToc", title: "TOC", upName:"upChangeRe1", upSequnce:2, align: 'right' }, - { id: "plInflWqltCod", title: "COD", upName:"upChangeRe1", upSequnce:3, align: 'right' }, - { id: "plInflWqltSs", title: "SS", upName:"upChangeRe1", upSequnce:4, align: 'right' }, - { id: "plInflWqltTn", title: "T-N", upName:"upChangeRe1", upSequnce:5, align: 'right' }, - { id: "plInflWqltTp", title: "T-P", upName:"upChangeRe1", upSequnce:6, align: 'right' }, - { id: "dsInflWqltBod", title: "BOD", upName:"upChangeRe2", upSequnce:1, align: 'right' }, - { id: "dsInflWqltToc", title: "TOC", upName:"upChangeRe2", upSequnce:2, align: 'right' }, - { id: "dsInflWqltCod", title: "COD", upName:"upChangeRe2", upSequnce:3, align: 'right' }, - { id: "dsInflWqltSs", title: "SS", upName:"upChangeRe2", upSequnce:4, align: 'right' }, - { id: "dsInflWqltTn", title: "T-N", upName:"upChangeRe2", upSequnce:5, align: 'right' }, - { id: "dsInflWqltTp", title: "T-P", upName:"upChangeRe2", upSequnce:6, align: 'right' }, - { id: "plEfflWqltBod", title: "BOD", upName:"upChangeRe3", upSequnce:1, align: 'right' }, - { id: "plEfflWqltToc", title: "TOC", upName:"upChangeRe3", upSequnce:2, align: 'right' }, - { id: "plEfflWqltCod", title: "COD", upName:"upChangeRe3", upSequnce:3, align: 'right' }, - { id: "plEfflWqltSs", title: "SS", upName:"upChangeRe3", upSequnce:4, align: 'right' }, - { id: "plEfflWqltTn", title: "T-N", upName:"upChangeRe3", upSequnce:5, align: 'right' }, - { id: "plEfflWqltTp", title: "T-P", upName:"upChangeRe3", upSequnce:6, align: 'right' }, + { id: "plInflWqltBod", title: "BOD", upName: "upChangeRe1", upSequnce: 1, align: 'right' }, + { id: "plInflWqltToc", title: "TOC", upName: "upChangeRe1", upSequnce: 2, align: 'right' }, + { id: "plInflWqltCod", title: "COD", upName: "upChangeRe1", upSequnce: 3, align: 'right' }, + { id: "plInflWqltSs", title: "SS", upName: "upChangeRe1", upSequnce: 4, align: 'right' }, + { id: "plInflWqltTn", title: "T-N", upName: "upChangeRe1", upSequnce: 5, align: 'right' }, + { id: "plInflWqltTp", title: "T-P", upName: "upChangeRe1", upSequnce: 6, align: 'right' }, + { id: "dsInflWqltBod", title: "BOD", upName: "upChangeRe2", upSequnce: 1, align: 'right' }, + { id: "dsInflWqltToc", title: "TOC", upName: "upChangeRe2", upSequnce: 2, align: 'right' }, + { id: "dsInflWqltCod", title: "COD", upName: "upChangeRe2", upSequnce: 3, align: 'right' }, + { id: "dsInflWqltSs", title: "SS", upName: "upChangeRe2", upSequnce: 4, align: 'right' }, + { id: "dsInflWqltTn", title: "T-N", upName: "upChangeRe2", upSequnce: 5, align: 'right' }, + { id: "dsInflWqltTp", title: "T-P", upName: "upChangeRe2", upSequnce: 6, align: 'right' }, + { id: "plEfflWqltBod", title: "BOD", upName: "upChangeRe3", upSequnce: 1, align: 'right' }, + { id: "plEfflWqltToc", title: "TOC", upName: "upChangeRe3", upSequnce: 2, align: 'right' }, + { id: "plEfflWqltCod", title: "COD", upName: "upChangeRe3", upSequnce: 3, align: 'right' }, + { id: "plEfflWqltSs", title: "SS", upName: "upChangeRe3", upSequnce: 4, align: 'right' }, + { id: "plEfflWqltTn", title: "T-N", upName: "upChangeRe3", upSequnce: 5, align: 'right' }, + { id: "plEfflWqltTp", title: "T-P", upName: "upChangeRe3", upSequnce: 6, align: 'right' }, ]; // 시설 수질정보 (리스트) -const gridListFcltyWaterQltyInfo:fcltyWaterQltyInfoProps[] = [ +const gridListFcltyWaterQltyInfo: fcltyWaterQltyInfoProps[] = [ { extnsSeqno: "최초", plInflWqltBod: "111.0", @@ -411,11 +415,22 @@ const gridListFcltyWaterQltyInfo:fcltyWaterQltyInfoProps[] = [ * Store - api (데이터 호출) ====================================*/ -// 시설정보 리스트 데이터 호출(구분 선택 후 시설명 있을 경우) -export const fetchFacilityListInit = async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string ): Promise<{data:facilityListProps[], count:number}> => { +/** + * 시설정보 리스트 데이터 호출(구분 선택 후 시설명 있을 경우) + * @param pageNo : 페이지 번호 + * @param pageSize : 페이지 크기 + * @param partValue : 구분값(행정명, 대유역, 환경청) + * @param nameCd : 행정코드(시도명, 시군구명, 단위유역) + * @param fcltCpct : 시설용량 + * @param oprtngSttsCd : 운영 상태 코드 + * @param facilityName : 시설명 + */ +export const fetchFacilityListInit = async (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string): Promise<{ data: facilityListProps[], count: number }> => { try { - const tailStr = partValue ? '/'+ (partValue==='G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : ''; - const url = '/prcsFclt/info/'+pageNo+'/'+pageSize+tailStr; + const tailStr = partValue ? '/' + (partValue === 'G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + + (facilityPartValue && facilityPartValue !== ''? '/' + facilityPartValue : '') + + (facilityName && facilityName !== ''? '/' + facilityName : '') : ''; + const url = '/prcsFclt/info/' + pageNo + '/' + pageSize + tailStr; const res = await getFetch('fo', url); res.data.forEach((item: Record<string, unknown>) => { item.daddr = item.addr ? item.addr : ''; @@ -427,42 +442,42 @@ export const fetchFacilityListInit = async (pageNo:number, pageSize:number, part return res; } catch (error) { console.error('시설정보 데이터를 가져오는데 실패했습니다:', error); - return {data: [], count: 0}; + return { data: [], count: 0 }; } }; -const fetchFacilityList = async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string ): Promise<{data:facilityListProps[], count:number}> => { +const fetchFacilityList = async (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string): Promise<{ data: facilityListProps[], count: number }> => { try { - const tailStr = partValue ? '/'+ (partValue==='G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : ''; - const url = '/prcsFclt/info/history/'+pageNo+'/'+pageSize+tailStr; + const tailStr = partValue ? '/' + (partValue === 'G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : ''; + const url = '/prcsFclt/info/history/' + pageNo + '/' + pageSize + tailStr; //const res = await getFetch('fo', '/prcsFclt/info/'+pageNo+'/'+pageSize+tailUrl); const res = await getFetch('fo', url); return res; } catch (error) { console.error('시설정보 데이터를 가져오는데 실패했습니다:', error); - return {data: [], count: 0}; + return { data: [], count: 0 }; } }; // 시설정보 리스트 데이터 호출 (대시보드용) -const fetchFacilityListSearching = async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string ): Promise<{data:facilityListProps[], count:number}> => { +const fetchFacilityListSearching = async (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string): Promise<{ data: facilityListProps[], count: number }> => { try { - const tailStr = partValue ? '/'+ (partValue==='G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : ''; - const url = '/prcsFclt/searching/'+pageNo+'/'+pageSize+tailStr; + const tailStr = partValue ? '/' + (partValue === 'G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : ''; + const url = '/prcsFclt/searching/' + pageNo + '/' + pageSize + tailStr; //const res = await getFetch('fo', '/prcsFclt/info/'+pageNo+'/'+pageSize+tailUrl); const res = await getFetch('fo', url); return res; } catch (error) { console.error('시설정보 데이터를 가져오는데 실패했습니다:', error); - return {data: [], count: 0}; + return { data: [], count: 0 }; } }; // 모달 // 시설정보 이력정보 데이터 호출 -const fetchFacilityHistInfo = async (facilityCd:string ): Promise<fcltyHistInfoProps[]> => { +const fetchFacilityHistInfo = async (facilityCd: string): Promise<fcltyHistInfoProps[]> => { try { - const res = await getFetch('fo', '/prcsFclt/hist/'+facilityCd); + const res = await getFetch('fo', '/prcsFclt/hist/' + facilityCd); const data = res.data; DataForm({ type: 'date', column: 'trlrnCmplDt', item: data as unknown as Record<string, unknown> }); DataForm({ type: 'date', column: 'eqpPrmsDt', item: data as unknown as Record<string, unknown> }); @@ -471,7 +486,7 @@ const fetchFacilityHistInfo = async (facilityCd:string ): Promise<fcltyHistInfoP DataForm({ type: 'number', column: 'sitArea', item: data as unknown as Record<string, unknown> }); DataForm({ type: 'number', column: 'trtDstrArea', item: data as unknown as Record<string, unknown> }); DataForm({ type: 'number', column: 'swrTrtPopl', item: data as unknown as Record<string, unknown> }); - return data; + return data; } catch (error) { console.error('시설정보 이력정보 데이터를 가져오는데 실패했습니다:', error); return []; @@ -479,9 +494,9 @@ const fetchFacilityHistInfo = async (facilityCd:string ): Promise<fcltyHistInfoP }; // 시설정보 수질정보 데이터 호출 -export const fetchFacilityWaterQltyInfo = async (facilityCd:string ): Promise<fcltyWaterQltyInfoProps[]> => { +export const fetchFacilityWaterQltyInfo = async (facilityCd: string): Promise<fcltyWaterQltyInfoProps[]> => { try { - const res = await getFetch('fo', '/prcsFclt/info/history/'+facilityCd); + const res = await getFetch('fo', '/prcsFclt/info/history/' + facilityCd); const data = res.data; return data; } catch (error) { @@ -504,97 +519,97 @@ export type ProcessFacilityActions = { * @param oprtngSttsCd : 운영 상태 코드 * @param facilityName : 시설명 */ - searchProcessFacility : (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string) => void; + searchProcessFacility: (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string) => void; /** * 시설정보 이력정보 데이터 호출 * @param facilityCd : 시설코드 */ - searchProcessFacilityHist : (facilityCd:string) => void; + searchProcessFacilityHist: (facilityCd: string) => void; /** * 시설정보 수질정보 데이터 호출 * @param facilityCd : 시설코드 */ - searchProcessFacilityWaterQlty : (facilityCd:string) => void; + searchProcessFacilityWaterQlty: (facilityCd: string) => void; /** * 시설정보 리스트 데이터 호출 (대시보드용) * @param pageNo : 페이지 번호 * @param pageSize : 페이지 크기 */ - searchProcessFacilitySearching : (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string) => void; + searchProcessFacilitySearching: (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string) => void; } export type ProcessFacilityType = { ProcessFacility: { isInit: boolean; - processFacilityLabelArray:string[]; + processFacilityLabelArray: string[]; }, - ProcessFacilityList : { + ProcessFacilityList: { isInit: boolean; - upHeadList1:TableUpperProps[]; - upHeadList2:TableUpperProps[]; - upHeadList3:TableUpperProps[]; - processFacilityList:facilityListProps[]; - processFacilityCount:number; - excelList : ExcelType[]; + upHeadList1: TableUpperProps[]; + upHeadList2: TableUpperProps[]; + upHeadList3: TableUpperProps[]; + processFacilityList: facilityListProps[]; + processFacilityCount: number; + excelList: ExcelType[]; isLoading: boolean; }, - ProcessFacilityDetail : { - isInit : boolean; - upHeadListBasicInfo : TableUpperProps[]; - gridListBasicInfo : BasicInfoProps[]; - upHeadListPlanInfo : TableUpperProps[]; - gridListPlanInfo : PlanInfoProps[]; - upHeadListFcltyAreaInfo : TableUpperProps[]; - gridListFcltyAreaInfo : fcltyAreaInfoProps[]; - - upHeadListFcltyHistInfo : TableUpperProps[]; - gridListFcltyHistInfo : fcltyHistInfoProps[]; - upHeadListFcltyWaterQltyInfo : TableUpperProps[]; - gridListFcltyWaterQltyInfo : fcltyWaterQltyInfoProps[]; + ProcessFacilityDetail: { + isInit: boolean; + upHeadListBasicInfo: TableUpperProps[]; + gridListBasicInfo: BasicInfoProps[]; + upHeadListPlanInfo: TableUpperProps[]; + gridListPlanInfo: PlanInfoProps[]; + upHeadListFcltyAreaInfo: TableUpperProps[]; + gridListFcltyAreaInfo: fcltyAreaInfoProps[]; - upHeadListLegalWaterQlty : TableUpperProps[]; - gridListLegalWaterQlty : LegalWaterQltyProps[]; + upHeadListFcltyHistInfo: TableUpperProps[]; + gridListFcltyHistInfo: fcltyHistInfoProps[]; + upHeadListFcltyWaterQltyInfo: TableUpperProps[]; + gridListFcltyWaterQltyInfo: fcltyWaterQltyInfoProps[]; + + upHeadListLegalWaterQlty: TableUpperProps[]; + gridListLegalWaterQlty: LegalWaterQltyProps[]; }, } -export const processFacilityInitState:ProcessFacilityType = { +export const processFacilityInitState: ProcessFacilityType = { ProcessFacility: { isInit: false, processFacilityLabelArray: ProcessFacilityLabelArray, }, - ProcessFacilityList : { - isInit : false, - upHeadList1 : upHeadList1, - upHeadList2 : upHeadList2, - upHeadList3 : upHeadList3, - processFacilityList : processFacilityList, - processFacilityCount:1, - excelList : [], - isLoading : true, + ProcessFacilityList: { + isInit: false, + upHeadList1: upHeadList1, + upHeadList2: upHeadList2, + upHeadList3: upHeadList3, + processFacilityList: processFacilityList, + processFacilityCount: 1, + excelList: [], + isLoading: true, }, - ProcessFacilityDetail : { - isInit : false, - upHeadListBasicInfo : upHeadListBasicInfo, - gridListBasicInfo : gridListBasicInfo, - upHeadListPlanInfo : upHeadListPlanInfo, - gridListPlanInfo : gridListPlanInfo, - upHeadListFcltyAreaInfo : upHeadListFcltyAreaInfo, - gridListFcltyAreaInfo : gridListFcltyAreaInfo, + ProcessFacilityDetail: { + isInit: false, + upHeadListBasicInfo: upHeadListBasicInfo, + gridListBasicInfo: gridListBasicInfo, + upHeadListPlanInfo: upHeadListPlanInfo, + gridListPlanInfo: gridListPlanInfo, + upHeadListFcltyAreaInfo: upHeadListFcltyAreaInfo, + gridListFcltyAreaInfo: gridListFcltyAreaInfo, - upHeadListFcltyHistInfo : upHeadListFcltyHistInfo, - gridListFcltyHistInfo : gridListFcltyHistInfo, - upHeadListFcltyWaterQltyInfo : upHeadListFcltyWaterQltyInfo, - gridListFcltyWaterQltyInfo : gridListFcltyWaterQltyInfo, + upHeadListFcltyHistInfo: upHeadListFcltyHistInfo, + gridListFcltyHistInfo: gridListFcltyHistInfo, + upHeadListFcltyWaterQltyInfo: upHeadListFcltyWaterQltyInfo, + gridListFcltyWaterQltyInfo: gridListFcltyWaterQltyInfo, - upHeadListLegalWaterQlty : upHeadListLegalWaterQlty, - gridListLegalWaterQlty : gridListLegalWaterQlty, + upHeadListLegalWaterQlty: upHeadListLegalWaterQlty, + gridListLegalWaterQlty: gridListLegalWaterQlty, } } @@ -603,8 +618,8 @@ type SetStateFunction = (state: ProcessFacilityType | ((state: ProcessFacilityTy export type ProcessFacilityStore = ProcessFacilityType & ProcessFacilityActions; -export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActions=(set: SetStateFunction) => { - return{ +export const processFacilityReducer: (set: SetStateFunction) => ProcessFacilityActions = (set: SetStateFunction) => { + return { // 메인페이지 /** @@ -612,7 +627,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio * @param pageNo : 페이지 번호 * @param pageSize : 페이지 크기 */ - searchProcessFacility: async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string) => { + searchProcessFacility: async (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string) => { set((state: ProcessFacilityType) => ({ ...state, ProcessFacilityList: { @@ -621,12 +636,12 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio }, })); const res = await fetchFacilityList(pageNo, pageSize, partValue, nameCd, fcltCpct, oprtngSttsCd, facilityPartValue, facilityName); - res.data.forEach((item:facilityListProps) => { - DataForm({ type: 'date', column: 'cmplDt', item:item as unknown as Record<string, unknown>}); - DataForm({ type: 'date', column: 'rnDt', item:item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'fctCpc', item:item as unknown as Record<string, unknown>}); + res.data.forEach((item: facilityListProps) => { + DataForm({ type: 'date', column: 'cmplDt', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'date', column: 'rnDt', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> }); }) - + res.data.forEach(item => { item.plInflWqltBod = item.plInflWqltBod ? Number(item.plInflWqltBod).toFixed(1) : ''; item.dsInflWqltBod = item.dsInflWqltBod ? Number(item.dsInflWqltBod).toFixed(1) : ''; @@ -634,7 +649,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio const excelDownList: ExcelType[] = []; - if(pageSize>5000){ + if (pageSize > 5000) { res.data.forEach(item => { excelDownList.push({ 시설코드: item.bscFctCd, @@ -646,7 +661,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio 시군구: item.gugun, 시설이름: item.bscFctNm, 주소: item.daddr, - "시설용량(m³/일)": item.fctCpc ? Number(item.fctCpc.toString().replaceAll(',','')) : '', + "시설용량(m³/일)": item.fctCpc ? Number(item.fctCpc.toString().replaceAll(',', '')) : '', "계획유입수질(mg/L)": item.plInflWqltBod ? Number(item.plInflWqltBod) : '', "설계유입수질(mg/L)": item.dsInflWqltBod ? Number(item.dsInflWqltBod) : '', 공법: item.trtFrfNm, @@ -656,27 +671,27 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio }) }) } - set((state:ProcessFacilityType) => ({ - ...state, - ProcessFacilityList: pageSize<5000?{ - ...state.ProcessFacilityList, - processFacilityList: res.data, - processFacilityCount: res.count, - isLoading: false, - }:{ - ...state.ProcessFacilityList, - excelList: excelDownList, - isLoading: false, - }, + set((state: ProcessFacilityType) => ({ + ...state, + ProcessFacilityList: pageSize < 5000 ? { + ...state.ProcessFacilityList, + processFacilityList: res.data, + processFacilityCount: res.count, + isLoading: false, + } : { + ...state.ProcessFacilityList, + excelList: excelDownList, + isLoading: false, + }, })) }, - + /** * 시설정보 리스트 데이터 호출 * @param pageNo : 페이지 번호 * @param pageSize : 페이지 크기 */ - searchProcessFacilitySearching: async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string) => { + searchProcessFacilitySearching: async (pageNo: number, pageSize: number, partValue?: string, nameCd?: string, fcltCpct?: string, oprtngSttsCd?: string, facilityPartValue?: string, facilityName?: string) => { set((state: ProcessFacilityType) => ({ ...state, ProcessFacilityList: { @@ -694,24 +709,24 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio item.plInflWqltBod = item.plInflWqltBod ? Number(item.plInflWqltBod).toFixed(1) : ''; item.dsInflWqltBod = item.dsInflWqltBod ? Number(item.dsInflWqltBod).toFixed(1) : ''; }) */ - set((state:ProcessFacilityType) => ({ - ...state, - ProcessFacilityList: { - ...state.ProcessFacilityList, - processFacilityList: res.data, - processFacilityCount: res.count, - isLoading: false, - }, + set((state: ProcessFacilityType) => ({ + ...state, + ProcessFacilityList: { + ...state.ProcessFacilityList, + processFacilityList: res.data, + processFacilityCount: res.count, + isLoading: false, + }, })) }, - + // 모달 이력정보 /** * 시설정보 이력정보 데이터 호출 * @param facilityCd : 시설코드 */ - searchProcessFacilityHist: async (facilityCd:string) => { + searchProcessFacilityHist: async (facilityCd: string) => { const promiseSet = await Promise.all([fetchFacilityHistInfo(facilityCd)]); const [facilityHistInfo] = promiseSet; // extnsSeqno를 기준으로 오름차순 정렬 @@ -727,7 +742,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio }); sortedHistInfo.forEach(item => { - if(item.extnsSeqno == '0'){ + if (item.extnsSeqno == '0') { item.extnsSeqno = '최초'; item.extnsNm = '신설'; } @@ -735,10 +750,10 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio // yyyy-MM-dd로 변환 sortedHistInfo.forEach(item => { - item.eqpPrmsDt = item.eqpPrmsDt ? item.eqpPrmsDt.substring(0,4) + '-' + item.eqpPrmsDt.substring(4,6) + '-' + item.eqpPrmsDt.substring(6,8) : ''; - item.trlrnCmplDt = item.trlrnCmplDt ? item.trlrnCmplDt.substring(0,4) + '-' + item.trlrnCmplDt.substring(4,6) + '-' + item.trlrnCmplDt.substring(6,8) : ''; - item.cmplDt = item.cmplDt ? item.cmplDt.substring(0,4) + '-' + item.cmplDt.substring(4,6) + '-' + item.cmplDt.substring(6,8) : ''; - item.rnDt = item.rnDt ? item.rnDt.substring(0,4) + '-' + item.rnDt.substring(4,6) + '-' + item.rnDt.substring(6,8) : ''; + item.eqpPrmsDt = item.eqpPrmsDt ? item.eqpPrmsDt.substring(0, 4) + '-' + item.eqpPrmsDt.substring(4, 6) + '-' + item.eqpPrmsDt.substring(6, 8) : ''; + item.trlrnCmplDt = item.trlrnCmplDt ? item.trlrnCmplDt.substring(0, 4) + '-' + item.trlrnCmplDt.substring(4, 6) + '-' + item.trlrnCmplDt.substring(6, 8) : ''; + item.cmplDt = item.cmplDt ? item.cmplDt.substring(0, 4) + '-' + item.cmplDt.substring(4, 6) + '-' + item.cmplDt.substring(6, 8) : ''; + item.rnDt = item.rnDt ? item.rnDt.substring(0, 4) + '-' + item.rnDt.substring(4, 6) + '-' + item.rnDt.substring(6, 8) : ''; }); // 마지막 이력 데이터의 값을 gridListPlanInfo에 업데이트 @@ -779,7 +794,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio * 시설정보 수질정보 데이터 호출 * @param facilityCd : 시설코드 */ - searchProcessFacilityWaterQlty: async (facilityCd:string) => { + searchProcessFacilityWaterQlty: async (facilityCd: string) => { const promiseSet = await Promise.all([fetchFacilityWaterQltyInfo(facilityCd)]); const [facilityWaterQltyInfo] = promiseSet; // extnsSeqno를 기준으로 오름차순 정렬 @@ -795,7 +810,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio }); sortedWaterQltyInfo.forEach(item => { - if(item.extnsSeqno == '0'){ + if (item.extnsSeqno == '0') { item.extnsSeqno = '최초'; } }); @@ -814,7 +829,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio DataForm({ type: 'float1', column: 'dsInflWqltSs', item: item as unknown as Record<string, unknown> }); DataForm({ type: 'float3', column: 'dsInflWqltTn', item: item as unknown as Record<string, unknown> }); DataForm({ type: 'float3', column: 'dsInflWqltTp', item: item as unknown as Record<string, unknown> }); - + DataForm({ type: 'float1', column: 'plEfflWqltBod', item: item as unknown as Record<string, unknown> }); DataForm({ type: 'float1', column: 'plEfflWqltToc', item: item as unknown as Record<string, unknown> }); DataForm({ type: 'float1', column: 'plEfflWqltCod', item: item as unknown as Record<string, unknown> }); @@ -829,10 +844,10 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio gridListLegalWaterQlty.push({ bod: facilityWaterQltyInfo[0]?.bod ?? 0, cod: Number(facilityWaterQltyInfo[0]?.cod ?? 0), - ss: Number(facilityWaterQltyInfo[0]?.ss ?? 0), + ss: Number(facilityWaterQltyInfo[0]?.ss ?? 0), tn: Number(facilityWaterQltyInfo[0]?.tn ?? 0), tp: Number(facilityWaterQltyInfo[0]?.tp ?? 0), - totClfgrpcnt : Number(facilityWaterQltyInfo[0]?.totClfgrpcnt ?? 0), + totClfgrpcnt: Number(facilityWaterQltyInfo[0]?.totClfgrpcnt ?? 0), }); gridListLegalWaterQlty.forEach(item => { @@ -851,7 +866,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio ProcessFacilityDetail: { ...state.ProcessFacilityDetail, gridListFcltyWaterQltyInfo: sortedWaterQltyInfo, - gridListLegalWaterQlty: gridListLegalWaterQlty, + gridListLegalWaterQlty: gridListLegalWaterQlty, gridListPlanInfo: [{ ...state.ProcessFacilityDetail.gridListPlanInfo[0], dsInflWqltBod: lastWaterQltyInfo?.dsInflWqltBod === 'NULL' || lastWaterQltyInfo?.dsInflWqltBod === undefined ? '' : lastWaterQltyInfo.dsInflWqltBod, @@ -875,7 +890,7 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio ProcessFacilityDetail: { ...state.ProcessFacilityDetail, gridListFcltyWaterQltyInfo: sortedWaterQltyInfo, - gridListLegalWaterQlty: gridListLegalWaterQlty, + gridListLegalWaterQlty: gridListLegalWaterQlty, gridListPlanInfo: [{ ...state.ProcessFacilityDetail.gridListPlanInfo[0], dsInflWqltBod: '', @@ -896,4 +911,4 @@ export const processFacilityReducer:(set:SetStateFunction)=>ProcessFacilityActio } } } -} \ No newline at end of file +} diff --git a/apps/fo/app/store/rdi/index.tsx b/apps/fo/app/store/rdi/index.tsx index 67c6efa1e..ff6a23053 100644 --- a/apps/fo/app/store/rdi/index.tsx +++ b/apps/fo/app/store/rdi/index.tsx @@ -322,15 +322,33 @@ export const rdiReducer:(set:SetStateFunction)=>RdiActions=(set: SetStateFunctio fcltCdList.push(item.bscFctCd); }); - const rdiiDetailListRes : any = await fetchFacilityRdiiDetailList(searchYear, fcltCdList); - console.log(rdiiDetailListRes); + // const rdiiDetailListRes : any = await fetchFacilityRdiiDetailList(searchYear, fcltCdList); + + //--------- + const rainDetailList: Record<string, any[]> = {}; + const results: any[] = []; + for (let idx = 0; idx < fcltCdList.length; idx += 100) { + const slicedItems = fcltCdList.slice(idx, idx + 100); + const result = await fetchFacilityRdiiDetailList(searchYear, slicedItems); + results.push(result); + } + //console.log(results); + // 결과를 합치기 + for (const res of results) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + if (!rainDetailList[keyStr]) rainDetailList[keyStr] = []; + rainDetailList[keyStr].push(...(value as any[])); + } + } + } + //------- + // console.log(rdiiDetailListRes); for (const item of rtnData) { - //const fcltyCd = item.bscFctCd; - //const res: any = await fetchFacilityWaterQltyListHist(searchYear, fcltyCd); - //const rdiiDetailList = res.data.wqltObjs; - const rdiiDetailList = rdiiDetailListRes?.data[item.bscFctCd]; - //console.log(rdiiDetailListRes); + // const rdiiDetailList = rdiiDetailListRes?.data[item.bscFctCd]; + const rdiiDetailList = rainDetailList[item.bscFctCd]; let rtnWinterInflBod = 0; let rtnSummerBodInfl = 0; diff --git a/apps/fo/app/store/sewageOccur/index.tsx b/apps/fo/app/store/sewageOccur/index.tsx index 69d2a4f50..dfd7e2bca 100644 --- a/apps/fo/app/store/sewageOccur/index.tsx +++ b/apps/fo/app/store/sewageOccur/index.tsx @@ -4,59 +4,59 @@ import { roundNumber } from "@/app/components/common/DataForm"; import { OSU_PERCENT } from "../common"; import type { TableUpperProps } from "../dashboard"; import { fetchFacilityListInit } from "../processFacility"; +// import { fetchFacilityRainDetailList } from "../facilityPrecipitation"; export type { TableUpperProps }; -/**==================================== - * Store - 타입 - ====================================*/ /** - * (1) 메인페이지 + * 운영실태 >오수발생량 추정 */ // 시설별 강수현황 export type SewageOccurProps = { - bscFctCd : string, - sidoNm : string, - sggNm : string, - lclgvrnmntNm : string, - bscFctNm : string, - daddr : string, - fctCpc : number, - waterUsage : number, - swageChangePercent : number, - estimationSewage : number, - sewageInputYear : number, - sewageInputChungchun : number, - rgnNm : string, - rvbsn : string, - lgbsn : string, - mdmbsn : string, - msrDt : string, + bscFctCd: string, + sidoNm: string, + sggNm: string, + lclgvrnmntNm: string, + bscFctNm: string, + daddr: string, + fctCpc: number, + waterUsage: number, + swageChangePercent: number, + estimationSewage: number, + sewageInputYear: number, + sewageInputChungchun: number, + rgnNm: string, + rvbsn: string, + lgbsn: string, + mdmbsn: string, + msrDt: string, } -export type modalTableType = { msrDt: string,waterUsage: number,swageChangePercent: number,estimationSewage: number}; +export type modalTableType = { msrDt: string, waterUsage: number, swageChangePercent: number, estimationSewage: number, } export type SewageOccurDetailProps = { -cnt : number, - sum : number, - yr:string, - usage:number, // 일평균 유입량 - cntWtrUsg : number, // 물사용량 일자수 - sumWtrUsg : number, // 물사용량 합 - avgWtrUsg:number, // 일평균 물사용량 - monWtrUsg:number, // 월 물사용량 + cnt: number, + sum: number, + yr: string, + usage: number, // 일평균 유입량 + cntWtrUsg: number, // 물사용량 일자수 + sumWtrUsg: number, // 물사용량 합 + avgWtrUsg: number, // 일평균 물사용량 + monWtrUsg: number, // 월 물사용량 + maxEstSewerage: number, // 최대 오수발생량 추정 + minEstSewerage: number, // 최소 오수발생량 추정 } export type WtrUsgYmProps = { - bscFctCd : string, // 처리구역시설코드 - levyYm : string, // 부과년월 - wtrUsg : number, // 월별 물사용량(m³/월) + bscFctCd: string, // 처리구역시설코드 + levyYm: string, // 부과년월 + wtrUsg: number, // 월별 물사용량(m³/월) } // 검색단 -const LabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; +const LabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; export interface ExcelType { 시설코드: string, - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', + 권역: string | '', + 단위유역: string | '', + 중권역: string | '', + 환경청: string | '', 시도: string, 시군구: string, 시설명: string, @@ -70,64 +70,64 @@ export interface ExcelType { } // 리스트 헤더 -const upHeadList1:TableUpperProps[] = [ - { id: "A", title: "행정구역", row:0, colSpan:2 }, - { id: "sidoNm", title: "시도", row:1, widthPercent: 8 }, - { id: "sggNm", title: "시군구", row:1, widthPercent: 8 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "waterUsage", title: "물사용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "swageChangePercent", title: "오수전환율\n(%)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, +const upHeadList1: TableUpperProps[] = [ + { id: "A", title: "행정구역", row: 0, colSpan: 2 }, + { id: "sidoNm", title: "시도", row: 1, widthPercent: 8 }, + { id: "sggNm", title: "시군구", row: 1, widthPercent: 8 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "waterUsage", title: "물사용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "swageChangePercent", title: "오수전환율\n(%)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, ]; -const upHeadList2:TableUpperProps[] = [ - { id: "A", title: "유역", row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", row:1, widthPercent: 6 }, - { id: "lgbsn", title: "단위유역", row:1, widthPercent: 6 }, - { id: "mdmbsn", title: "중권역", row:1, widthPercent: 6 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "waterUsage", title: "물사용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "swageChangePercent", title: "오수전환율", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, +const upHeadList2: TableUpperProps[] = [ + { id: "A", title: "유역", row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", row: 1, widthPercent: 6 }, + { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 6 }, + { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 6 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "waterUsage", title: "물사용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "swageChangePercent", title: "오수전환율", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, ]; -const upHeadList3:TableUpperProps[] = [ - { id: "A", title: "유역", row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", row:1, widthPercent: 5 }, - { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 8 }, - { id: "B", title: "행정구역", row:0, colSpan:2 }, - { id: "sidoNm", title: "시도", row:1, widthPercent: 6 }, - { id: "sggNm", title: "시군구", row:1, widthPercent: 6 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "waterUsage", title: "물사용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "swageChangePercent", title: "오수전환율", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, +const upHeadList3: TableUpperProps[] = [ + { id: "A", title: "유역", row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 5 }, + { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "B", title: "행정구역", row: 0, colSpan: 2 }, + { id: "sidoNm", title: "시도", row: 1, widthPercent: 6 }, + { id: "sggNm", title: "시군구", row: 1, widthPercent: 6 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "waterUsage", title: "물사용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "swageChangePercent", title: "오수전환율", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "estimationSewage", title: "오수발생량 추정\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "sewageInputYear", title: "연간 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "sewageInputChungchun", title: "청천시 하수유입량\n(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, ]; -const upHeadListSewageOccurDetail:TableUpperProps[] = [ +const upHeadListSewageOccurDetail: TableUpperProps[] = [ { id: "msrDt", title: "년월" }, - { id: "waterUsage", title: "물사용량(m³/일)", align:"right" }, - { id: "swageChangePercent", title: "오수전환율(%)", align:"right" }, - { id: "estimationSewage", title: "오수발생량 추정(m³/일)", row:0, rowSpan:2, align:"right" }, + { id: "waterUsage", title: "물사용량(m³/일)", align: "right" }, + { id: "swageChangePercent", title: "오수전환율(%)", align: "right" }, + { id: "estimationSewage", title: "오수발생량 추정(m³/일)", row: 0, rowSpan: 2, align: "right" }, ]; -const sewageOccurList:SewageOccurProps[] = [] +const sewageOccurList: SewageOccurProps[] = [] -const tempSewageOccurList:modalTableType[] = [] +const tempSewageOccurList: modalTableType[] = [] /** * yyyymm 형태의 문자열을 받아 해당 월의 일수를 반환 @@ -135,20 +135,20 @@ const tempSewageOccurList:modalTableType[] = [] * @returns {number} 해당 월의 일수 (예: 28, 29, 30, 31) */ function getDaysInMonth(yyyymm) { - if (typeof yyyymm !== 'string' || yyyymm.length !== 6) { - throw new Error("입력은 'yyyymm' 형식의 문자열이어야 합니다. 예: '202502'"); - } + if (typeof yyyymm !== 'string' || yyyymm.length !== 6) { + throw new Error("입력은 'yyyymm' 형식의 문자열이어야 합니다. 예: '202502'"); + } - const year = parseInt(yyyymm.slice(0, 4), 10); // "2025" -> 2025 - const month = parseInt(yyyymm.slice(4, 6), 10); // "02" -> 2 + const year = parseInt(yyyymm.slice(0, 4), 10); // "2025" -> 2025 + const month = parseInt(yyyymm.slice(4, 6), 10); // "02" -> 2 - if (isNaN(year) || isNaN(month) || month < 1 || month > 12) { - throw new Error("유효한 년/월이 아닙니다."); - } + if (isNaN(year) || isNaN(month) || month < 1 || month > 12) { + throw new Error("유효한 년/월이 아닙니다."); + } - // Date(year, month, 0) -> 전달의 마지막 날 - // month에 '다음달'을 넣고, 일(day)에 0을 주면 해당 월의 마지막 날짜가 됨 - return new Date(year, month, 0).getDate(); + // Date(year, month, 0) -> 전달의 마지막 날 + // month에 '다음달'을 넣고, 일(day)에 0을 주면 해당 월의 마지막 날짜가 됨 + return new Date(year, month, 0).getDate(); } /**==================================== * Store - API @@ -174,9 +174,9 @@ function getDaysInMonth(yyyymm) { // } // 시설들 유량 정보 조회 -export const fetchSweageOccurListFlow = async (searchYear: string|undefined, fcltCdList: string[]): Promise<SewageOccurProps | null> => { +export const fetchSweageOccurListFlow = async (searchYear: string | undefined, fcltCdList: string[]): Promise<SewageOccurProps | null> => { try { - const res = await postFetch('fo', '/fclt/flow/'+searchYear, fcltCdList); + const res = await postFetch('fo', '/fclt/flow/' + searchYear, fcltCdList); return res; } catch (error) { console.error('시설정보 오수발생량 상세 데이터를 가져오는데 실패했습니다:', error); @@ -184,9 +184,9 @@ export const fetchSweageOccurListFlow = async (searchYear: string|undefined, fcl } }; // 시설별 물사용량 정보 조회 -export const fetchWtrUsgYmList = async (searchYear: string|undefined, fcltCdList: string[]): Promise<WtrUsgYmProps | null> => { +export const fetchWtrUsgYmList = async (searchYear: string | undefined, fcltCdList: string[]): Promise<WtrUsgYmProps | null> => { try { - const res = await postFetch('fo', '/fclt/flow/wtrUsgYm/'+searchYear, fcltCdList); + const res = await postFetch('fo', '/fclt/flow/wtrUsgYm/' + searchYear, fcltCdList); return res; } catch (error) { console.error('시설들 월별 물사용량 데이터를 가져오는데 실패했습니다:', error); @@ -205,10 +205,10 @@ export type SewageOccurActions = { export type SewageOccurType = { SewageOccurSearch: { isInit: boolean; - labelArray:string[]; + labelArray: string[]; }, - SewageOccur : { + SewageOccur: { isInit: boolean; upHeadList1: TableUpperProps[]; upHeadList2: TableUpperProps[]; @@ -219,7 +219,7 @@ export type SewageOccurType = { isLoading: boolean; }, - SewageOccurDetail : { + SewageOccurDetail: { isInit: boolean; upHeadListSewageOccurDetail: TableUpperProps[]; gridListSewageOccurDetail: modalTableType[]; @@ -232,18 +232,18 @@ export const sewageOccurInitState: SewageOccurType = { labelArray: LabelArray, }, - SewageOccur : { - isInit : false, - upHeadList1 : upHeadList1, - upHeadList2 : upHeadList2, - upHeadList3 : upHeadList3, - sewageOccurList : sewageOccurList, - sewageOccurCount : 2, - excelList : [], - isLoading : true, + SewageOccur: { + isInit: false, + upHeadList1: upHeadList1, + upHeadList2: upHeadList2, + upHeadList3: upHeadList3, + sewageOccurList: sewageOccurList, + sewageOccurCount: 2, + excelList: [], + isLoading: true, }, - SewageOccurDetail : { + SewageOccurDetail: { isInit: false, upHeadListSewageOccurDetail: upHeadListSewageOccurDetail, gridListSewageOccurDetail: tempSewageOccurList, @@ -277,69 +277,105 @@ export type FlowResponse = { count: number; } -export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { +export const sewageOccurReducer: (set: any) => SewageOccurActions = (set: any) => { return { searchSewageOccurList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { const facilityInitData = await fetchFacilityListInit(pageNo, pageSize, part, nameCd, fcltCpct, operationValue, fcltyNm); - if(pageSize < 5000){ + if (pageSize < 5000) { set((state: SewageOccurType) => ({ ...state, SewageOccur: { ...state.SewageOccur, - sewageOccurList : [], - sewageOccurCount : facilityInitData.count, - excelList : [], + sewageOccurList: [], + sewageOccurCount: facilityInitData.count, + excelList: [], isLoading: true }, })); - }else{ + } else { set((state: SewageOccurType) => ({ ...state, SewageOccur: { ...state.SewageOccur, - excelList : [], + excelList: [], isLoading: true }, })); } - + const fcltylist = facilityInitData.data; // 시설목록 - const fcltCdList : string[] = []; // 시설코드목록 - const sewageOccurPropsList : SewageOccurProps[] = []; // 오수발생량시설목록 + const fcltCdList: string[] = []; // 시설코드목록 + const sewageOccurPropsList: SewageOccurProps[] = []; // 오수발생량시설목록 fcltylist.forEach(item => { fcltCdList.push(item.bscFctCd); sewageOccurPropsList.push({ - bscFctCd : item.bscFctCd, - sidoNm : item.sido, - sggNm : item.gugun, - lclgvrnmntNm : '', - bscFctNm : item.bscFctNm, - daddr : item.daddr, - fctCpc : parseFloat(item.fctCpc.toString().replace(/,/g, '')), - waterUsage : 0, - swageChangePercent : 0, - estimationSewage : 0, - sewageInputYear : 0, - sewageInputChungchun : 0, - rgnNm : item.rgnNm, - rvbsn : item.rvbsn, - lgbsn : item.lgbsn, - mdmbsn : item.mdmbsn, - msrDt : '', + bscFctCd: item.bscFctCd, + sidoNm: item.sido, + sggNm: item.gugun, + lclgvrnmntNm: '', + bscFctNm: item.bscFctNm, + daddr: item.daddr, + fctCpc: parseFloat(item.fctCpc.toString().replace(/,/g, '')), + waterUsage: 0, + swageChangePercent: 0, + estimationSewage: 0, + sewageInputYear: 0, + sewageInputChungchun: 0, + rgnNm: item.rgnNm, + rvbsn: item.rvbsn, + lgbsn: item.lgbsn, + mdmbsn: item.mdmbsn, + msrDt: '', }); }); + + // const soccurDetailRes: any = await fetchSweageOccurListFlow(searchYear, fcltCdList); + // const wtrUsgYmRes: any = await fetchWtrUsgYmList(searchYear, fcltCdList); - const soccurDetailRes : any = await fetchSweageOccurListFlow(searchYear, fcltCdList); - const wtrUsgYmRes : any = await fetchWtrUsgYmList(searchYear, fcltCdList); - // const wtrUsgMap : Record<string, number> = {}; - // wtrUsgRes.data.forEach(fclt => { - // wtrUsgMap[fclt.bscFctCd] = fclt.wtrUsg; - // }); + //--------- + const rainDetailList: Record<string, any[]> = {}; + const wtrDetailList: Record<string, any[]> = {}; + const results: any[] = []; + const wtrResults: any[] = []; + for (let idx = 0; idx < fcltCdList.length; idx += 100) { + const slicedItems = fcltCdList.slice(idx, idx + 100); + const result = await fetchSweageOccurListFlow(searchYear, slicedItems); + const wtrResult = await fetchWtrUsgYmList(searchYear, slicedItems); + results.push(result); + wtrResults.push(wtrResult); + } + //console.log(results); + // 결과를 합치기 + for (const res of results) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + //rainDetailList[keyStr] = [...(rainDetailList[keyStr] || []), ...(value as any[])]; + if (!rainDetailList[keyStr]) rainDetailList[keyStr] = []; + rainDetailList[keyStr].push(...(value as any[])); + } + } + } + for (const res of wtrResults) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + // wtrDetailList[keyStr] = [...(wtrDetailList[keyStr] || []), ...(value as any[])]; + if (!wtrDetailList[keyStr]) wtrDetailList[keyStr] = []; + wtrDetailList[keyStr].push(...(value as any[])); + } + } + } + //------- + + let maxEstimationSewage = 0, minEstimationSewage = 999999999; for (const item of sewageOccurPropsList) { - - const detailList = soccurDetailRes?.data[item.bscFctCd]; - const wtrUsgYmList = wtrUsgYmRes?.data[item.bscFctCd]; - + + //const detailList = soccurDetailRes?.data[item.bscFctCd]; + // const wtrUsgYmList = wtrUsgYmRes?.data[item.bscFctCd]; + const detailList = rainDetailList[item.bscFctCd]; + const wtrUsgYmList = wtrDetailList[item.bscFctCd]; + item.swageChangePercent = 90; let sumChungcunDay = 0; // 청천일수 let chungcunSumFlow = 0;// 청천시 하수유입량 @@ -348,18 +384,18 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { detailList?.forEach(item2 => { if (item2.infoRnDay == null || item2.infoRnDay == 'x') item2.infoRnDay = 0; - if(item2.infoRnDay >= 3) { + if (item2.infoRnDay >= 3) { rainEffectDay = 2; - }else{ + } else { rainEffectDay--; } - if(rainEffectDay<0){ //청천시 + if (rainEffectDay < 0) { //청천시 chungcunSumFlow += item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0;//Number(item2.inflAmnt); sumChungcunDay += 1; } totWaterUsage += Number(item2.inflAmnt); }); - + let wtrUsgYearTotal = 0; // 연간 물사용량 let wtrUsgYearDays = 0; // 연간 물사용 일수 wtrUsgYmList?.forEach(item3 => { @@ -370,25 +406,23 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { // console.log("totWaterUsage:", totWaterUsage); const count = detailList ? detailList.length : 0; - const avg = count == 0 ? 0 : totWaterUsage / count; // 일평균 유입량 - item.waterUsage = parseFloat((avg * 0.65).toFixed(2)); // 물사용량 = 일평균 유입량 * 65% - if(wtrUsgYearTotal > 0 && wtrUsgYearDays > 0) { - item.waterUsage = wtrUsgYearDays>0? wtrUsgYearTotal / wtrUsgYearDays: 0; // 일평균 물사용량 - } - item.sewageInputYear = parseFloat(avg.toFixed(2)); // 연간 하수유입량(m³/일) - item.sewageInputChungchun = parseFloat((chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay)).toFixed(2)); // 청천시 하수유입량 - item.estimationSewage = parseFloat((item.waterUsage * OSU_PERCENT).toFixed(2)); // 오수발생량 추정(m³/일) - if ( item.bscFctCd === '11200PB001R' ) { - console.log('chungcunSumFlow:', chungcunSumFlow); - console.log('sumChungcunDay:', sumChungcunDay); + const avg = count == 0 ? 0 : totWaterUsage / count; // 연간 하수유입량(m³/일) + item.waterUsage = parseFloat((avg * 0.65).toFixed(2)); // 물사용량 = 연간 하수유입량(m³/일) * 65% : 실제 수용가의 물사용량 없는 경우 + if (wtrUsgYearTotal > 0 && wtrUsgYearDays > 0) { + item.waterUsage = wtrUsgYearDays > 0 ? wtrUsgYearTotal / wtrUsgYearDays : 0; // 일평균 물사용량: 실제 수용가의 물사용량 } + item.sewageInputYear = parseFloat(avg.toFixed(2)); // 연간 하수유입량(m³/일) + item.sewageInputChungchun = parseFloat((chungcunSumFlow === 0 ? 0 : (chungcunSumFlow / sumChungcunDay)).toFixed(2)); // 청천시 하수유입량 + item.estimationSewage = parseFloat((item.waterUsage * OSU_PERCENT).toFixed(2)); // 오수발생량 추정(m³/일) = 물사용량(m³/일) * 오수전환율 + if (item.estimationSewage > maxEstimationSewage) maxEstimationSewage = item.estimationSewage; + if (item.estimationSewage < minEstimationSewage) minEstimationSewage = item.estimationSewage; // 반올림 처리 ["fctCpc", "waterUsage", "swageChangePercent", "estimationSewage", "sewageInputYear", "sewageInputChungchun"].forEach( (key) => (item[key] = roundNumber(item[key], 0)) ); } - if(pageSize >= 5000){ + if (pageSize >= 5000) { const excelDownList: ExcelType[] = []; sewageOccurPropsList.forEach((item: SewageOccurProps) => { excelDownList.push({ @@ -414,114 +448,32 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { SewageOccur: { ...state.SewageOccur, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })); - }else{ + } else { set((state: SewageOccurType) => ({ ...state, SewageOccur: { ...state.SewageOccur, - sewageOccurList : sewageOccurPropsList, - sewageOccurCount : facilityInitData.count, + sewageOccurList: sewageOccurPropsList, + sewageOccurCount: facilityInitData.count, isLoading: false, excelList: [], }, })); } - /* fetchSewageOccurList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm).then(async (res) => { - const excelDownList: ExcelType[] = []; - const data = res.data; - // Promise.all을 사용하여 모든 비동기 작업이 완료될 때까지 기다림 - const calData = await Promise.all(data.map(async (item: SewageOccurProps) => { - item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; - item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.swageChangePercent = 90; - - const bscFctCd = item.bscFctCd; - const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as FlowResponse; - - let sumChungcunDay = 0; - let chungcunSumFlow = 0; - let totWaterUsage = 0; - - // 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일) - const rainEffectData = CalcRainEffect(res2.data); - - rainEffectData.forEach((item: FlowDataItem) => { - - if(Number(item.rainEffectDay) === 0){ - chungcunSumFlow += Number(item.inflAmnt); - sumChungcunDay += 1; - } - - totWaterUsage += Number(item.inflAmnt); - }); - const avg = totWaterUsage /res2.count; - item.waterUsage = parseFloat((avg * 0.65).toFixed(2)); - item.sewageInputYear = parseFloat(avg.toFixed(2)); - item.sewageInputChungchun = parseFloat((chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay)).toFixed(2)); - item.estimationSewage = parseFloat((item.waterUsage * (item.swageChangePercent/100)).toFixed(2)); - return item; - })); - - calData.forEach((item: SewageOccurProps) => { - DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'waterUsage', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'swageChangePercent', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'estimationSewage', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'sewageInputYear', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'sewageInputChungchun', item: item as unknown as Record<string, unknown> }); - }); - - if(pageSize>5000){ - calData.forEach((item: SewageOccurProps) => { - excelDownList.push({ - 시설코드: item.bscFctCd, - 권역: item.rvbsn, - 단위유역: item.lgbsn, - 중권역: item.mdmbsn, - 환경청: item.rgnNm, - 시설명: item.bscFctNm, - 시도: item.sidoNm, - 시군구: item.sggNm, - 주소: item.daddr, - "시설용량(m³/일)": item.fctCpc, - "물사용량(m³/일)": item.waterUsage, - "오수전환율(%)": item.swageChangePercent, - "오수발생량추정(m³/일)": item.estimationSewage, - "연간하수유입량(m³/일)": item.sewageInputYear, - "청천시하수유입량(m³/일)": item.sewageInputChungchun, - }); - }); - } - // 조회 데이터 설정 - set((state: any) => ({ - ...state, - SewageOccur: pageSize<5000?{ - ...state.SewageOccur, - sewageOccurList: calData, - sewageOccurCount: res.count, - isLoading : false, - }:{ - ...state.SewageOccur, - excelList: excelDownList, - isLoading : false, - }, - })) - }); */ }, searchSewageOccurDetailList: async (bscFctCd: string, searchYear: string, startYm?: string, endYm?: string) => { const fcltCdList = [bscFctCd]; - const wtrUsgRes : any = await fetchWtrUsgYmList(searchYear, fcltCdList); + const wtrUsgRes: any = await fetchWtrUsgYmList(searchYear, fcltCdList); // 수용가의 실제 물사용량 const res = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear); const monthData: SewageOccurDetailProps[] = []; - let tot = 0, cnt= 0; + let tot = 0, cnt = 0; - if(startYm) { + if (startYm) { res.data = res.data.filter((item: FlowDataItem) => { const month = item.msrDt?.substring(0, 6); return month >= startYm; @@ -533,7 +485,7 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { }); } } - if(endYm) { + if (endYm) { res.data = res.data.filter((item: FlowDataItem) => { const month = item.msrDt?.substring(0, 6); return month <= endYm; @@ -555,15 +507,17 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { monthData[monthIdx].sum += Number(item.inflAmnt); monthData[monthIdx].cnt++; } else { - monthData[monthIdx] = {cnt:1, sum:Number(item.inflAmnt), yr:item.msrDt.substring(0,4) - , usage:0, cntWtrUsg:0, sumWtrUsg:0, avgWtrUsg:0, monWtrUsg:0}; + monthData[monthIdx] = { + cnt: 1, sum: Number(item.inflAmnt), yr: item.msrDt.substring(0, 4) + , usage: 0, cntWtrUsg: 0, sumWtrUsg: 0, avgWtrUsg: 0, monWtrUsg: 0, maxEstSewerage: 0, minEstSewerage: 999999999 + }; } } - tot += Number(item.inflAmnt) + tot += Number(item.inflAmnt); // 연간 유입량 cnt++ }); - // 물사용량 + // 수용가 실제 물사용량 let totWtrUsg = 0; let cntWtrUsg = 0; if (wtrUsgRes && wtrUsgRes.data && wtrUsgRes.data[bscFctCd]) { @@ -574,59 +528,57 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { if (typeof monthData[monthIdx] === 'object') { monthData[monthIdx].sumWtrUsg += Number(item.wtrUsg); monthData[monthIdx].cntWtrUsg += getDaysInMonth(item.levyYm); - } + } } totWtrUsg += Number(item.wtrUsg); cntWtrUsg += getDaysInMonth(item.levyYm); }); } - - const tableData:modalTableType[] = [] - let maxWaterUsage = 0; - let minWaterUsage = 9999999999; + const tableData: modalTableType[] = [] + + let maxWaterUsage = 0, minWaterUsage = 9999999999; + let maxEstSewage = 0, minEstSewage = 9999999999; let avgWaterUsage = 0; monthData.forEach((item, index) => { - item.avgWtrUsg = item.sum/item.cnt *0.65; // 일평균 물사용량 = 일평균유입수 * 0.65 - if (item.sumWtrUsg > 0 && item.cntWtrUsg > 0) item.avgWtrUsg = item.sumWtrUsg / item.cntWtrUsg; + item.avgWtrUsg = item.sum / item.cnt * 0.65; // 일평균 물사용량 = 일평균유입수 * 0.65 : 수용가 실제 물사용량이 없으면 + if (item.sumWtrUsg > 0 && item.cntWtrUsg > 0) item.avgWtrUsg = item.sumWtrUsg / item.cntWtrUsg; //수용가 실제 물사용량이 있는 경우 + const estSewage = Math.round(item.avgWtrUsg * OSU_PERCENT * 1) / 1; // 오수발생량 추정 tableData.push({ - msrDt: item.yr+'-'+(index+1>=10?String(index+1):'0'+(index+1)), - waterUsage: Math.round(item.avgWtrUsg*1) /1, - swageChangePercent: OSU_PERCENT*100,//(Math.round((item.usage/item.usage*OSU_PERCENT) * 100) / 100) * 100, - estimationSewage: Math.round(item.avgWtrUsg*OSU_PERCENT * 1) / 1 + msrDt: item.yr + '-' + (index + 1 >= 10 ? String(index + 1) : '0' + (index + 1)), + waterUsage: Math.round(item.avgWtrUsg * 1) / 1, + swageChangePercent: OSU_PERCENT * 100, //오수전환율 (Math.round((item.usage/item.usage*OSU_PERCENT) * 100) / 100) * 100, + estimationSewage: estSewage, }) - if (item.avgWtrUsg > maxWaterUsage) { - maxWaterUsage = item.avgWtrUsg; - } - - if (item.avgWtrUsg < minWaterUsage) { - minWaterUsage = item.avgWtrUsg; - } + if (item.avgWtrUsg > maxWaterUsage) maxWaterUsage = item.avgWtrUsg; // 물사용량 최대 + if (item.avgWtrUsg < minWaterUsage) minWaterUsage = item.avgWtrUsg; // 물사용량 최소 + if (maxEstSewage < estSewage) maxEstSewage = estSewage; // 오수발생량 최대 + if (minEstSewage > estSewage) minEstSewage = estSewage; // 오수발생량 최소 }); - avgWaterUsage = tot/cnt*0.65 + avgWaterUsage = tot / cnt * 0.65 if (totWtrUsg > 0 && cntWtrUsg > 0) avgWaterUsage = totWtrUsg / cntWtrUsg; - const newData= [ - { - msrDt: '평균', - waterUsage: roundNumber(Math.round(avgWaterUsage*1) /1,0), - swageChangePercent: OSU_PERCENT*100, - estimationSewage: roundNumber(Math.round(avgWaterUsage*OSU_PERCENT*1) /1,0), - }, - { - msrDt: '최대', - waterUsage: roundNumber(Math.round(maxWaterUsage*100) /100,0), - swageChangePercent: OSU_PERCENT*100, - estimationSewage: roundNumber(Math.round(maxWaterUsage*OSU_PERCENT*1) /1, 0), - }, - { - msrDt: '최소', - waterUsage: roundNumber(Math.round(minWaterUsage*100) /100,0), - swageChangePercent: OSU_PERCENT*100, - estimationSewage: roundNumber(Math.round(minWaterUsage*OSU_PERCENT*1) /1,0), - }, - ...tableData + const newData = [ + { + msrDt: '평균', + waterUsage: roundNumber(Math.round(avgWaterUsage * 1) / 1, 0), + swageChangePercent: OSU_PERCENT * 100, + estimationSewage: roundNumber(Math.round(avgWaterUsage * OSU_PERCENT * 1) / 1, 0), + }, + { + msrDt: '최대', + waterUsage: roundNumber(Math.round(maxWaterUsage * 100) / 100, 0), + swageChangePercent: OSU_PERCENT * 100, + estimationSewage: roundNumber(maxEstSewage / 1, 0), + }, + { + msrDt: '최소', + waterUsage: roundNumber(Math.round(minWaterUsage * 100) / 100, 0), + swageChangePercent: OSU_PERCENT * 100, + estimationSewage: roundNumber(minEstSewage / 1, 0), + }, + ...tableData ] set((state: SewageOccurType) => ({ diff --git a/apps/fo/app/store/store.tsx b/apps/fo/app/store/store.tsx index f7f2222c6..c7064a40e 100644 --- a/apps/fo/app/store/store.tsx +++ b/apps/fo/app/store/store.tsx @@ -42,8 +42,9 @@ import { export type FoStore = CommonStore & tableType & DashBoardStore & ProcessFacilityStore & ProcessAreaStore & FlowRateSearchStore & FacilityPrecipitationStore & FacilityWaterQltyStore & PlanSewageStore & SewageOccurStore & UnderWaterStore & RdiStore; export type FoState = CommonType & tableState & ProcessFacilityType & ProcessAreaType & FacilityPrecipitationType & FlowRateSearchType & DashBoardType & FacilityWaterQltyType & PlanSewageType & SewageOccurType & UnderWaterType & RdiType; -export const StoreZus = - (initState: FoState = { ...commonState, +export const StoreZus = + (initState: FoState = { + ...commonState, ...tableStateExport , ...processFacilityInitState , ...processAreaInitState @@ -57,22 +58,22 @@ export const StoreZus = , ...rdiInitState } ) => { - return createStore<FoStore>()((set) => { - return { - ...initState, - ...CommonReducer(set), - ...tableActionsExport(set), - ...dashBoardReducer(set), - ...processFacilityReducer(set), - ...processAreaReducer(set), - ...facilityPrecipitationReducer(set), - ...flowRateSearchReducer(set), - ...facilityWaterQltyReducer(set), - ...planSewageReducer(set), - ...sewageOccurReducer(set), - ...underWaterReducer(set), - ...rdiReducer(set), - } - }) -} + return createStore<FoStore>()((set, get) => { + return { + ...initState, + ...CommonReducer(set), + ...tableActionsExport(set), + ...dashBoardReducer(set, get), + ...processFacilityReducer(set), + ...processAreaReducer(set), + ...facilityPrecipitationReducer(set), + ...flowRateSearchReducer(set), + ...facilityWaterQltyReducer(set), + ...planSewageReducer(set), + ...sewageOccurReducer(set), + ...underWaterReducer(set), + ...rdiReducer(set), + } + }) + } //store 등록하는 부분 \ No newline at end of file diff --git a/apps/fo/app/store/underWaterInput/index.tsx b/apps/fo/app/store/underWaterInput/index.tsx index c6a2167d8..806b12e2b 100644 --- a/apps/fo/app/store/underWaterInput/index.tsx +++ b/apps/fo/app/store/underWaterInput/index.tsx @@ -5,20 +5,21 @@ import type { ExcelType, UnderWaterInputModalProps, UnderWaterProps, WtrUsgYmPro // import CalcRainEffect from "@/app/components/common/CalcRainEffect"; import { OSU_PERCENT } from "../common"; import { fetchFacilityListInit } from "../processFacility"; +import { getDaysInMonth } from "@/app/components/common/DataForm"; export type { TableUpperProps }; // SewageOccurDetailProps 타입을 여기에 정의 export type SewageOccurDetailProps = { - cnt : number, - sum : number, - yr:string, - usage:number, // 일평균 유입량 - sunnyCnt : number, - sunnyInflAmnt : number, - sunnyAvgInflAmnt : number, // 청천시 일평균 유입량 - cntWtrUsg : number, // 물사용량 일자수 - sumWtrUsg : number, // 물사용량 합 - avgWtrUsg:number, // 일평균 물사용량 + cnt: number, + sum: number, + yr: string, + usage: number, // 일평균 유입량 + sunnyCnt: number, + sunnyInflAmnt: number, + sunnyAvgInflAmnt: number, // 청천시 일평균 유입량 + cntWtrUsg: number, // 물사용량 일자수 + sumWtrUsg: number, // 물사용량 합 + avgWtrUsg: number, // 일평균 물사용량 } /**==================================== * Store - 타입 @@ -35,145 +36,145 @@ export type SewageOccurDetailProps = { /** * (1) 메인페이지 - */ + */ // 검색단 -const LabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; +const LabelArray: string[] = ['구분', '시도', '시군구', '용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; // 리스트 헤더 -const upHeadList1:TableUpperProps[] = [ - { id: "A", title: "행정구역", row:0, colSpan:2 }, - { id: "sidoNm", title: "시도", row:1, widthPercent: 8 }, - { id: "sggNm", title: "시군구", row:1, widthPercent: 8 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 10 }, - { id: "C", title: "지하수 유입량 추정", align:"right", row:0, colSpan:3 }, - { id: "underWaterInputYear", title: "연간\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "properYn", title: "적정여부", row:0, rowSpan:2, widthPercent: 8 }, +const upHeadList1: TableUpperProps[] = [ + { id: "A", title: "행정구역", row: 0, colSpan: 2 }, + { id: "sidoNm", title: "시도", row: 1, widthPercent: 8 }, + { id: "sggNm", title: "시군구", row: 1, widthPercent: 8 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "C", title: "지하수 유입량 추정", align: "right", row: 0, colSpan: 3 }, + { id: "underWaterInputYear", title: "연간\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "properYn", title: "적정여부", row: 0, rowSpan: 2, widthPercent: 8 }, ]; -const upHeadList2:TableUpperProps[] = [ - { id: "A", title: "유역", row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", row:1, widthPercent: 5 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 8 }, - { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 10 }, - { id: "C", title: "지하수 유입량 추정", align:"right", row:0, colSpan:3 }, - { id: "underWaterInputYear", title: "연간\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align:"right", row:1, widthPercent: 8 }, - { id: "properYn", title: "적정여부", row:0, rowSpan:2, widthPercent: 8 }, +const upHeadList2: TableUpperProps[] = [ + { id: "A", title: "유역", row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", row: 1, widthPercent: 5 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "C", title: "지하수 유입량 추정", align: "right", row: 0, colSpan: 3 }, + { id: "underWaterInputYear", title: "연간\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align: "right", row: 1, widthPercent: 8 }, + { id: "properYn", title: "적정여부", row: 0, rowSpan: 2, widthPercent: 8 }, ]; -const upHeadList3:TableUpperProps[] = [ - { id: "A", title: "유역", row:0, colSpan:3 }, - { id: "rvbsn", title: "권역", upName:"upChangeRe1", upSequnce:1, row:1, widthPercent: 5 }, - { id: "lgbsn", title: "단위유역", upName:"upChangeRe1", upSequnce:2, row:1, widthPercent: 5 }, - { id: "mdmbsn", title: "중권역", upName:"upChangeRe1", upSequnce:3, row:1, widthPercent: 5 }, - { id: "rgnNm", title: "환경청", row:0, rowSpan:2, widthPercent: 8 }, - { id: "B", title: "행정구역", row:0, colSpan:2 }, - { id: "sidoNm", title: "시도", row:1, widthPercent: 6 }, - { id: "sggNm", title: "시군구", row:1, widthPercent: 6 }, - { id: "bscFctNm", title: "시설명", row:0, rowSpan:2, widthPercent: 8 }, - { id: "daddr", title: "주소", row:0, rowSpan:2 }, - { id: "fctCpc", title: "시설용량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 6 }, - { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align:"right", row:0, rowSpan:2, widthPercent: 10 }, - { id: "C", title: "지하수 유입량 추정", align:"right", row:0, colSpan:3 }, - { id: "underWaterInputYear", title: "연간\n(m³/일)", align:"right", row:1, widthPercent: 6}, - { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align:"right", row:1, widthPercent: 6 }, - { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align:"right", row:1, widthPercent: 6 }, - { id: "properYn", title: "적정여부", row:0, rowSpan:2, widthPercent: 6 }, +const upHeadList3: TableUpperProps[] = [ + { id: "A", title: "유역", row: 0, colSpan: 3 }, + { id: "rvbsn", title: "권역", upName: "upChangeRe1", upSequnce: 1, row: 1, widthPercent: 5 }, + { id: "lgbsn", title: "단위유역", upName: "upChangeRe1", upSequnce: 2, row: 1, widthPercent: 5 }, + { id: "mdmbsn", title: "중권역", upName: "upChangeRe1", upSequnce: 3, row: 1, widthPercent: 5 }, + { id: "rgnNm", title: "환경청", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "B", title: "행정구역", row: 0, colSpan: 2 }, + { id: "sidoNm", title: "시도", row: 1, widthPercent: 6 }, + { id: "sggNm", title: "시군구", row: 1, widthPercent: 6 }, + { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2, widthPercent: 8 }, + { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, + { id: "fctCpc", title: "시설용량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 6 }, + { id: "inflAmnt", title: "연평균유입하수량(m³/일)", align: "right", row: 0, rowSpan: 2, widthPercent: 10 }, + { id: "C", title: "지하수 유입량 추정", align: "right", row: 0, colSpan: 3 }, + { id: "underWaterInputYear", title: "연간\n(m³/일)", align: "right", row: 1, widthPercent: 6 }, + { id: "underWaterInputSummer", title: "하절기\n(m³/일)", align: "right", row: 1, widthPercent: 6 }, + { id: "underWaterInputWinter", title: "동절기\n(m³/일)", align: "right", row: 1, widthPercent: 6 }, + { id: "properYn", title: "적정여부", row: 0, rowSpan: 2, widthPercent: 6 }, ]; // 모달 -const upHeadListUnderWaterDetail:TableUpperProps[] = [ +const upHeadListUnderWaterDetail: TableUpperProps[] = [ { id: "msrDt", title: "날짜" }, - { id: "realSewageInput", title: "청천시 실제 하수유입량\n(m³/일)", align:"right" }, - { id: "sewageOccur", title: "오수발생량\n(m³/일)", align:"right" }, - { id: "underWaterInput", title: "지하수 유입량\n(m³/일)", align:"right" }, + { id: "realSewageInput", title: "청천시 실제 하수유입량\n(m³/일)", align: "right" }, + { id: "sewageOccur", title: "오수발생량\n(m³/일)", align: "right" }, + { id: "underWaterInput", title: "지하수 유입량\n(m³/일)", align: "right" }, ]; -const underWaterList:UnderWaterProps[] = [ +const underWaterList: UnderWaterProps[] = [ ] -const underWaterDetailList:UnderWaterInputModalProps[] = [ +const underWaterDetailList: UnderWaterInputModalProps[] = [ { - msrDt : "2024-01", - realSewageInput : 178007, - sewageOccur : 10.5, - underWaterInput : 80.6, + msrDt: "2024-01", + realSewageInput: 178007, + sewageOccur: 10.5, + underWaterInput: 80.6, }, { - msrDt : "2024-02", - realSewageInput : 123007, - sewageOccur : 10.7, - underWaterInput : 80.6, + msrDt: "2024-02", + realSewageInput: 123007, + sewageOccur: 10.7, + underWaterInput: 80.6, }, { - msrDt : "2024-03", - realSewageInput : 213007, - sewageOccur : 10.1, - underWaterInput : 80.6, + msrDt: "2024-03", + realSewageInput: 213007, + sewageOccur: 10.1, + underWaterInput: 80.6, }, { - msrDt : "2024-04", - realSewageInput : 83007, - sewageOccur : 9.57, - underWaterInput : 80.6, + msrDt: "2024-04", + realSewageInput: 83007, + sewageOccur: 9.57, + underWaterInput: 80.6, }, { - msrDt : "2024-05", - realSewageInput : 15007, - sewageOccur : 3.57, - underWaterInput : 80.6, + msrDt: "2024-05", + realSewageInput: 15007, + sewageOccur: 3.57, + underWaterInput: 80.6, }, { - msrDt : "2024-06", - realSewageInput : 123007, - sewageOccur : '', - underWaterInput : 80.6, + msrDt: "2024-06", + realSewageInput: 123007, + sewageOccur: '', + underWaterInput: 80.6, }, { - msrDt : "2024-07", - realSewageInput : 101007, - sewageOccur : 1, - underWaterInput : 80.6, + msrDt: "2024-07", + realSewageInput: 101007, + sewageOccur: 1, + underWaterInput: 80.6, }, { - msrDt : "2024-08", - realSewageInput : 103807, - sewageOccur : 10.57, - underWaterInput : '', + msrDt: "2024-08", + realSewageInput: 103807, + sewageOccur: 10.57, + underWaterInput: '', }, { - msrDt : "2024-09", - realSewageInput : 9007, - sewageOccur : 10.57, - underWaterInput : 80.6, + msrDt: "2024-09", + realSewageInput: 9007, + sewageOccur: 10.57, + underWaterInput: 80.6, }, { - msrDt : "2024-10", - realSewageInput : 163007, - sewageOccur : 10.57, - underWaterInput : 80.6, + msrDt: "2024-10", + realSewageInput: 163007, + sewageOccur: 10.57, + underWaterInput: 80.6, }, { - msrDt : "2024-11", - realSewageInput : 123007, - sewageOccur : 10.57, - underWaterInput : 80.6, + msrDt: "2024-11", + realSewageInput: 123007, + sewageOccur: 10.57, + underWaterInput: 80.6, }, { - msrDt : "2024-12", - realSewageInput : 1007, - sewageOccur : 10.57, - underWaterInput : 80.6, + msrDt: "2024-12", + realSewageInput: 1007, + sewageOccur: 10.57, + underWaterInput: 80.6, }, ] /**==================================== @@ -185,8 +186,8 @@ export interface FlowDataItem { inflAmnt: number | string; infoRnDay: number; msrDt: string; - rainEffectDay? : number; - sumFlow? : number | string | ''; + rainEffectDay?: number; + sumFlow?: number | string | ''; } export interface ApiResponse { @@ -216,9 +217,9 @@ const fetchUnderWaterList = async (pageNo: number, pageSize: number, searchYear: // } // 시설들 유량 정보 조회 -export const fetchDetailListFlow = async (searchYear: string|undefined, fcltCdList: string[]): Promise<UnderWaterProps | null> => { +export const fetchDetailListFlow = async (searchYear: string | undefined, fcltCdList: string[]): Promise<UnderWaterProps | null> => { try { - const res = await postFetch('fo', '/fclt/flow/'+searchYear, fcltCdList); + const res = await postFetch('fo', '/fclt/flow/' + searchYear, fcltCdList); return res; } catch (error) { console.error('시설정보 오수발생량 상세 데이터를 가져오는데 실패했습니다:', error); @@ -226,9 +227,9 @@ export const fetchDetailListFlow = async (searchYear: string|undefined, fcltCdLi } }; // 시설들 월별 물사용량 정보 조회 -export const fetchWtrUsgYmList = async (searchYear: string|undefined, fcltCdList: string[]): Promise<WtrUsgYmProps | null> => { +export const fetchWtrUsgYmList = async (searchYear: string | undefined, fcltCdList: string[]): Promise<WtrUsgYmProps | null> => { try { - const res = await postFetch('fo', '/fclt/flow/wtrUsgYm/'+searchYear, fcltCdList); + const res = await postFetch('fo', '/fclt/flow/wtrUsgYm/' + searchYear, fcltCdList); return res; } catch (error) { console.error('시설들 월별 물사용량 데이터를 가져오는데 실패했습니다:', error); @@ -241,16 +242,16 @@ export const fetchWtrUsgYmList = async (searchYear: string|undefined, fcltCdList ====================================*/ export type UnderWaterActions = { searchUnderWaterList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void; - unserWaterModal:(bscFctCd:string, searchYear:string, startYm:string, endYm:string)=>void + underWaterModal: (bscFctCd: string, searchYear: string, startYm: string, endYm: string) => void } export type UnderWaterType = { UnderWaterSearch: { isInit: boolean; - labelArray:string[]; + labelArray: string[]; }, - UnderWater : { + UnderWater: { isInit: boolean; upHeadList1: TableUpperProps[]; upHeadList2: TableUpperProps[]; @@ -261,7 +262,7 @@ export type UnderWaterType = { isLoading: boolean; }, - UnderWaterDetail : { + UnderWaterDetail: { isInit: boolean; upHeadListUnderWaterDetail: TableUpperProps[]; gridListUnderWaterDetail: UnderWaterInputModalProps[]; @@ -274,30 +275,30 @@ export const underWaterInitState: UnderWaterType = { labelArray: LabelArray, }, - UnderWater : { - isInit : false, - upHeadList1 : upHeadList1, - upHeadList2 : upHeadList2, - upHeadList3 : upHeadList3, - underWaterList : underWaterList, - underWaterCount : 4, - excelList : [], - isLoading : true, + UnderWater: { + isInit: false, + upHeadList1: upHeadList1, + upHeadList2: upHeadList2, + upHeadList3: upHeadList3, + underWaterList: underWaterList, + underWaterCount: 4, + excelList: [], + isLoading: true, }, - UnderWaterDetail : { + UnderWaterDetail: { isInit: false, upHeadListUnderWaterDetail: upHeadListUnderWaterDetail, gridListUnderWaterDetail: underWaterDetailList, } } -const dataFormSetting=(item : UnderWaterProps)=> { +const dataFormSetting = (item: UnderWaterProps) => { DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> }); DataForm({ type: 'float0', column: 'inflAmnt', item: item as unknown as Record<string, unknown> }); - DataForm({ type: 'float0', column: 'underWaterInputYear', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'underWaterInputSummer', item: item as unknown as Record<string, unknown>}); - DataForm({ type: 'float0', column: 'underWaterInputWinter', item: item as unknown as Record<string, unknown>}); + DataForm({ type: 'float0', column: 'underWaterInputYear', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'underWaterInputSummer', item: item as unknown as Record<string, unknown> }); + DataForm({ type: 'float0', column: 'underWaterInputWinter', item: item as unknown as Record<string, unknown> }); } // 병렬 처리 제한용 헬퍼 @@ -310,47 +311,34 @@ const dataFormSetting=(item : UnderWaterProps)=> { // } // return results; // } -/** - * yyyymm 형태의 문자열을 받아 해당 월의 일수를 반환 - * @param {string} yyyymm - 예: "202502", "202412" - * @returns {number} 해당 월의 일수 (예: 28, 29, 30, 31) - */ -function getDaysInMonth(yyyymm) { - if (typeof yyyymm !== 'string' || yyyymm.length !== 6) { - throw new Error("입력은 'yyyymm' 형식의 문자열이어야 합니다. 예: '202502'"); - } - - const year = parseInt(yyyymm.slice(0, 4), 10); // "2025" -> 2025 - const month = parseInt(yyyymm.slice(4, 6), 10); // "02" -> 2 - - if (isNaN(year) || isNaN(month) || month < 1 || month > 12) { - throw new Error("유효한 년/월이 아닙니다."); - } - - // Date(year, month, 0) -> 전달의 마지막 날 - // month에 '다음달'을 넣고, 일(day)에 0을 주면 해당 월의 마지막 날짜가 됨 - return new Date(year, month, 0).getDate(); -} export type UnderWaterStore = UnderWaterType & UnderWaterActions; -export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterType) => void)=>UnderWaterActions=(set: (fn: (state: UnderWaterType) => UnderWaterType) => void) => { +export const underWaterReducer: (set: (fn: (state: UnderWaterType) => UnderWaterType) => void) => UnderWaterActions = (set: (fn: (state: UnderWaterType) => UnderWaterType) => void) => { return { searchUnderWaterList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { + set((state: UnderWaterType) => ({ + ...state, + UnderWater: { + ...state.UnderWater, + isLoading: true, + }, + })); + const facilityInitData = await fetchFacilityListInit(pageNo, pageSize, part, nameCd, fcltCpct, operationValue, fcltyNm); - - if(pageSize < 5000){ + + if (pageSize < 5000) { set((state: UnderWaterType) => ({ ...state, UnderWater: { ...state.UnderWater, - underWaterList : facilityInitData.data, - underWaterCount : facilityInitData.count, - isInit : true, + underWaterList: facilityInitData.data, + underWaterCount: facilityInitData.count, + isInit: true, isLoading: true, }, })); - }else{ + } else { set((state: UnderWaterType) => ({ ...state, UnderWater: { @@ -359,43 +347,79 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy }, })); } - + const fcltylist = facilityInitData.data; - const fcltCdList : string[] = []; // 시설코드목록 - const underWaterPropsList : UnderWaterProps[] = []; // 오수발생량시설목록 + const fcltCdList: string[] = []; // 시설코드목록 + const underWaterPropsList: UnderWaterProps[] = []; // 오수발생량시설목록 fcltylist.forEach(item => { fcltCdList.push(item.bscFctCd); underWaterPropsList.push({ - bscFctCd : item.bscFctCd, - sidoNm : item.sido, - sggNm : item.gugun, - lclgvrnmntNm : '', - bscFctNm : item.bscFctNm, - daddr : item.daddr, - fctCpc : parseFloat(item.fctCpc.toString().replace(/,/g, '')), - inflAmnt : 0, - waterUsage : 0, - underWaterInputYear : 0, - underWaterInputSummer : 0, - underWaterInputWinter : 0, - properYn : '', - infoRnDay : 0, - rgnNm : item.rgnNm, - rvbsn : item.rvbsn, - lgbsn : item.lgbsn, - mdmbsn : item.mdmbsn, - msrDt : '', + bscFctCd: item.bscFctCd, + sidoNm: item.sido, + sggNm: item.gugun, + lclgvrnmntNm: '', + bscFctNm: item.bscFctNm, + daddr: item.daddr, + fctCpc: parseFloat(item.fctCpc.toString().replace(/,/g, '')), + inflAmnt: 0, + waterUsage: 0, + underWaterInputYear: 0, + underWaterInputSummer: 0, + underWaterInputWinter: 0, + properYn: '', + infoRnDay: 0, + rgnNm: item.rgnNm, + rvbsn: item.rvbsn, + lgbsn: item.lgbsn, + mdmbsn: item.mdmbsn, + msrDt: '', }); }); - - const soccurDetailRes : any = await fetchDetailListFlow(searchYear, fcltCdList); - const wtrUsgYmRes : any = await fetchWtrUsgYmList(searchYear, fcltCdList); - + + // const soccurDetailRes: any = await fetchDetailListFlow(searchYear, fcltCdList); + // const wtrUsgYmRes: any = await fetchWtrUsgYmList(searchYear, fcltCdList); + //--------- + const rainDetailList: Record<string, any[]> = {}; + const wtrDetailList: Record<string, any[]> = {}; + const results: any[] = []; + const wtrResults: any[] = []; + for (let idx = 0; idx < fcltCdList.length; idx += 100) { + const slicedItems = fcltCdList.slice(idx, idx + 100); + const result = await fetchDetailListFlow(searchYear, slicedItems); + const wtrResult = await fetchWtrUsgYmList(searchYear, slicedItems); + results.push(result); + wtrResults.push(wtrResult); + } + //console.log(results); + // 결과를 합치기 + for (const res of results) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + //rainDetailList[keyStr] = [...(rainDetailList[keyStr] || []), ...(value as any[])]; + if (!rainDetailList[keyStr]) rainDetailList[keyStr] = []; + rainDetailList[keyStr].push(...(value as any[])); + } + } + } + for (const res of wtrResults) { + if (res.status === 200 && res.data) { + for (const [key, value] of Object.entries(res.data)) { + const keyStr = String(key); + // wtrDetailList[keyStr] = [...(wtrDetailList[keyStr] || []), ...(value as any[])]; + if (!wtrDetailList[keyStr]) wtrDetailList[keyStr] = []; + wtrDetailList[keyStr].push(...(value as any[])); + } + } + } + //------- for (const item of underWaterPropsList) { - - const detailList = soccurDetailRes?.data[item.bscFctCd]; - const wtrUsgYmList = wtrUsgYmRes?.data[item.bscFctCd]; - + + // const detailList = soccurDetailRes?.data[item.bscFctCd]; + // const wtrUsgYmList = wtrUsgYmRes?.data[item.bscFctCd]; + const detailList = rainDetailList[item.bscFctCd]; + const wtrUsgYmList = wtrDetailList[item.bscFctCd]; + let chungcunSumFlow = 0; // 청천유입량 let sumChungcunDay = 0; // 청천일수 let sumSummerChungcunDay = 0; // 하절기 청천일수 @@ -407,39 +431,39 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy let totWinterUsage = 0; // 동절기 하수유입량 let totSummerDays = 0; // 하절기 일수 let totWinterDays = 0; // 동절기 일수 - + const summerMonth = ['06', '07', '08']; const winterMonth = ['01', '02']; - + let rainEffectDay = 0; detailList?.forEach(item2 => { if (item2.infoRnDay == null || item2.infoRnDay.trim() == 'x') item2.infoRnDay = 0; - if(Number(item2.infoRnDay) >= 3) { + if (Number(item2.infoRnDay) >= 3) { rainEffectDay = 2; - }else{ + } else { rainEffectDay--; } - if(rainEffectDay<0){ //청천시 + if (rainEffectDay < 0) { //청천시 chungcunSumFlow += Number(item2.inflAmnt); sumChungcunDay += 1; - if(item2.msrDt.substring(4,6) ==='06' || item2.msrDt.substring(4,6) ==='07' || item2.msrDt.substring(4,6) ==='08'){ + if (item2.msrDt.substring(4, 6) === '06' || item2.msrDt.substring(4, 6) === '07' || item2.msrDt.substring(4, 6) === '08') { // 하절기 6,7,8월 sumSummerChungcunDay += 1; sumSummerChungcunFlow += Number(item2.inflAmnt); //item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; - }else if(item2.msrDt.substring(4,6) ==='01' || item2.msrDt.substring(4,6) ==='02'){ + } else if (item2.msrDt.substring(4, 6) === '01' || item2.msrDt.substring(4, 6) === '02') { // 동절기 1,2월 sumWinterChungcunDay += 1; sumWinterChungcunFlow += Number(item2.inflAmnt); //item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; } } totWaterUsage += Number(item2.inflAmnt); - if(item2.msrDt.substring(4,6) ==='06' || item2.msrDt.substring(4,6) ==='07' || item2.msrDt.substring(4,6) ==='08'){ + if (item2.msrDt.substring(4, 6) === '06' || item2.msrDt.substring(4, 6) === '07' || item2.msrDt.substring(4, 6) === '08') { // 하절기 6,7,8월 totSummerDays += 1; totSummerUsage += Number(item2.inflAmnt); //item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; - }else if(item2.msrDt.substring(4,6) ==='01' || item2.msrDt.substring(4,6) ==='02'){ + } else if (item2.msrDt.substring(4, 6) === '01' || item2.msrDt.substring(4, 6) === '02') { // 동절기 1,2월 totWinterDays += 1; totWinterUsage += Number(item2.inflAmnt); //item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; @@ -447,8 +471,8 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy }); const count = detailList ? detailList.length : 0; - const avg = count == 0 ? 0 : totWaterUsage / count; // 일평균 유입량 - + const avg = count == 0 ? 0 : totWaterUsage / count; // 연간 일평균 유입량 + let wtrUsgYearTotal = 0; // 연간 물사용량 let summerWtrUsgTot = 0; // 하절기 물사용량 let winterWtrUsgTot = 0; // 동절기 물사용량 @@ -459,52 +483,52 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy const days = getDaysInMonth(item3.levyYm); wtrUsgYearTotal += Number(item3.wtrUsg); wtrUsgYearDays += days; - if(summerMonth.includes(item3.levyYm.slice(-2))){ + if (summerMonth.includes(item3.levyYm.slice(-2))) { summerWtrUsgTot += Number(item3.wtrUsg); summerWtrUsgDays += days; - }else if(winterMonth.includes(item3.levyYm.slice(-2))){ + } else if (winterMonth.includes(item3.levyYm.slice(-2))) { winterWtrUsgTot += Number(item3.wtrUsg); winterWtrUsgDays += days; } }); - + if (wtrUsgYearTotal > 0) { // 물사용량이 있는 경우 - const usage = chungcunSumFlow&&chungcunSumFlow>0?chungcunSumFlow / sumChungcunDay:0; // 청천시 일평균 유입량 - const summerUsage = sumSummerChungcunFlow?sumSummerChungcunFlow / sumSummerChungcunDay:0; // 하절기 청천시 일평균 유입량 - const winterUsage = sumWinterChungcunFlow?sumWinterChungcunFlow / sumWinterChungcunDay:0; // 동절기 청천시 일평균 유입량 - const wtrUsg = wtrUsgYearDays>0? wtrUsgYearTotal / wtrUsgYearDays: 0; // 일평균 물사용량 - const summerWtrUsg = summerWtrUsgTot?summerWtrUsgTot / summerWtrUsgDays:0; // 하절기 일평균 물사용량 - const winterWtrUsg = winterWtrUsgTot?winterWtrUsgTot / winterWtrUsgDays:0; // 동절기 일평균 물사용량 - + const usage = chungcunSumFlow && chungcunSumFlow > 0 ? chungcunSumFlow / sumChungcunDay : 0; // 청천시 일평균 유입량 + const summerUsage = sumSummerChungcunFlow ? sumSummerChungcunFlow / sumSummerChungcunDay : 0; // 하절기 청천시 일평균 유입량 + const winterUsage = sumWinterChungcunFlow ? sumWinterChungcunFlow / sumWinterChungcunDay : 0; // 동절기 청천시 일평균 유입량 + const wtrUsg = wtrUsgYearDays > 0 ? wtrUsgYearTotal / wtrUsgYearDays : 0; // 일평균 물사용량 + const summerWtrUsg = summerWtrUsgTot ? summerWtrUsgTot / summerWtrUsgDays : 0; // 하절기 일평균 물사용량 + const winterWtrUsg = winterWtrUsgTot ? winterWtrUsgTot / winterWtrUsgDays : 0; // 동절기 일평균 물사용량 + item.inflAmnt = roundNumber(avg, 0); - item.underWaterInputYear = Math.round((usage-(wtrUsg*OSU_PERCENT)) * 1) / 1; // 연간지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) - item.underWaterInputSummer = Math.round((summerUsage-(summerWtrUsg*OSU_PERCENT)) * 1) / 1; // 하절기지하수유입량(m³/일) - item.underWaterInputWinter = Math.round((winterUsage-(winterWtrUsg*OSU_PERCENT)) * 1) / 1; // 동절기지하수유입량(m³/일) - item.properYn = Math.round( (Number(usage-(wtrUsg*OSU_PERCENT)) / Number(item.inflAmnt)) * 100 * 1 ) / 1 > 20?'초과유입':'적정'; // 적정여부 + item.underWaterInputYear = Math.round((usage - (wtrUsg * OSU_PERCENT)) * 1) / 1; // 연간지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) + item.underWaterInputSummer = Math.round((summerUsage - (summerWtrUsg * OSU_PERCENT)) * 1) / 1; // 하절기지하수유입량(m³/일) + item.underWaterInputWinter = Math.round((winterUsage - (winterWtrUsg * OSU_PERCENT)) * 1) / 1; // 동절기지하수유입량(m³/일) + item.properYn = Math.round((Number(usage - (wtrUsg * OSU_PERCENT)) / Number(item.inflAmnt)) * 100 * 1) / 1 > 20 ? '초과유입' : '적정'; // 적정여부 } else { // 물사용량이 없는 경우, 일평균 유입량으로 물사용량 추정(물사용량 = 유입량*0.65) - const usage = chungcunSumFlow&&chungcunSumFlow>0?chungcunSumFlow / sumChungcunDay:0; // 청천시 일평균 유입량 - const summerUsage = sumSummerChungcunFlow?sumSummerChungcunFlow / sumSummerChungcunDay:0; // 하절기 청천시 일평균 유입량 - const winterUsage = sumWinterChungcunFlow?sumWinterChungcunFlow / sumWinterChungcunDay:0; // 동절기 청천시 일평균 유입량 + const usage = chungcunSumFlow && chungcunSumFlow > 0 ? chungcunSumFlow / sumChungcunDay : 0; // 청천시 일평균 유입량 + const summerUsage = sumSummerChungcunFlow ? sumSummerChungcunFlow / sumSummerChungcunDay : 0; // 하절기 청천시 일평균 유입량 + const winterUsage = sumWinterChungcunFlow ? sumWinterChungcunFlow / sumWinterChungcunDay : 0; // 동절기 청천시 일평균 유입량 const wtrUsg = avg * 0.65; // 일평균 물사용량 - const summerWtrUsg = totSummerDays>0? (totSummerUsage / totSummerDays)*0.65:0; // 하절기 일평균 물사용량 - const winterWtrUsg = totWinterDays>0? (totWinterUsage / totWinterDays)*0.65:0; // 동절기 일평균 물사용량 - + const summerWtrUsg = totSummerDays > 0 ? (totSummerUsage / totSummerDays) * 0.65 : 0; // 하절기 일평균 물사용량 + const winterWtrUsg = totWinterDays > 0 ? (totWinterUsage / totWinterDays) * 0.65 : 0; // 동절기 일평균 물사용량 + item.inflAmnt = roundNumber(avg, 0); //item.underWaterInputYear = Math.round((usage-(usage*0.65*OSU_PERCENT)) * 1) / 1; // 연간지하수유입량(m³/일) - item.underWaterInputYear = Math.round((usage-(wtrUsg*OSU_PERCENT)) * 1) / 1; // 연간지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) - item.underWaterInputSummer = Math.round((summerUsage-(summerWtrUsg*OSU_PERCENT)) * 1) / 1; // 하절기지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) - item.underWaterInputWinter = Math.round((winterUsage-(winterWtrUsg*OSU_PERCENT)) * 1) / 1; // 동절기지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) - item.properYn = Math.round( (Number(usage-(wtrUsg*OSU_PERCENT)) / Number(item.inflAmnt)) * 100 * 1 ) / 1 > 20?'초과유입':'적정'; // 적정여부 - // 반올림 처리 - // ["fctCpc", "inflAmnt", "underWaterInputYear", "underWaterInputSummer", "underWaterInputWinter"].forEach( - // (key) => (item[key] = roundNumber(item[key], 0)) - // ); + item.underWaterInputYear = Math.round((usage - (wtrUsg * OSU_PERCENT)) * 1) / 1; // 연간지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) + item.underWaterInputSummer = Math.round((summerUsage - (summerWtrUsg * OSU_PERCENT)) * 1) / 1; // 하절기지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) + item.underWaterInputWinter = Math.round((winterUsage - (winterWtrUsg * OSU_PERCENT)) * 1) / 1; // 동절기지하수유입량(m³/일) = 청천시 실제유입량 - (물사용량 × 오수전환율) + item.properYn = Math.round((Number(usage - (wtrUsg * OSU_PERCENT)) / Number(item.inflAmnt)) * 100 * 1) / 1 > 20 ? '초과유입' : '적정'; // 적정여부 + // 반올림 처리 + // ["fctCpc", "inflAmnt", "underWaterInputYear", "underWaterInputSummer", "underWaterInputWinter"].forEach( + // (key) => (item[key] = roundNumber(item[key], 0)) + // ); } } - if(pageSize>= 5000){ + if (pageSize >= 5000) { const excelDownList: ExcelType[] = []; - underWaterPropsList.forEach((item : any) => { + underWaterPropsList.forEach((item: any) => { dataFormSetting(item) item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); excelDownList.push({ @@ -530,10 +554,10 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy UnderWater: { ...state.UnderWater, excelList: excelDownList, - isLoading : false, + isLoading: false, }, })); - }else{ + } else { set((state: UnderWaterType) => ({ ...state, UnderWater: { @@ -541,21 +565,21 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy underWaterList: underWaterPropsList, underWaterCount: facilityInitData.count, excelList: [], - isLoading : false, + isLoading: false, }, })); } }, - - unserWaterModal:async (bscFctCd, searchYear, startYm, endYm)=>{ + + underWaterModal: async (bscFctCd, searchYear, startYm, endYm) => { const fcltCdList = [bscFctCd]; - const wtrUsgRes : any = await fetchWtrUsgYmList(searchYear, fcltCdList); + const wtrUsgRes: any = await fetchWtrUsgYmList(searchYear, fcltCdList); const res = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear); const monthData: SewageOccurDetailProps[] = []; let tot = 0, cnt = 0; let sunnyTot = 0, sunnyCnt = 0; - - if(startYm) { + + if (startYm) { res.data = res.data.filter((item: FlowDataItem) => { const month = item.msrDt?.substring(0, 6); return month >= startYm; @@ -567,7 +591,7 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy }); } } - if(endYm) { + if (endYm) { res.data = res.data.filter((item: FlowDataItem) => { const month = item.msrDt?.substring(0, 6); return month <= endYm; @@ -582,36 +606,40 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy let rainEffectDay = 0; res.data?.forEach(item2 => { if (item2.infoRnDay == null || item2.infoRnDay.trim() == 'x') item2.infoRnDay = 0; - if(Number(item2.infoRnDay) >= 3) { + if (Number(item2.infoRnDay) >= 3) { rainEffectDay = 2; - }else{ + } else { rainEffectDay--; } - + const month = item2.msrDt?.substring(4, 6); const monthIdx = month ? Number(month) - 1 : -1; - if (monthIdx >= 0 && (monthIdx>=Number(startYm.substring(4,6))-1 && monthIdx <= Number(endYm.substring(4, 6))-1)) { + if (monthIdx >= 0 && (monthIdx >= Number(startYm.substring(4, 6)) - 1 && monthIdx <= Number(endYm.substring(4, 6)) - 1)) { if (typeof monthData[monthIdx] === 'object') { monthData[monthIdx].sum += Number(item2.inflAmnt); // 유입량 합 monthData[monthIdx].cnt++; } else { - monthData[monthIdx] = {cnt:1, sum:Number(item2.inflAmnt), yr:item2.msrDt.substring(0,4) - , usage:0, sunnyCnt:0, sunnyInflAmnt:0, sunnyAvgInflAmnt: 0, cntWtrUsg:0, sumWtrUsg:0, avgWtrUsg:0}; + monthData[monthIdx] = { + cnt: 1, sum: Number(item2.inflAmnt), yr: item2.msrDt.substring(0, 4) + , usage: 0, sunnyCnt: 0, sunnyInflAmnt: 0, sunnyAvgInflAmnt: 0, cntWtrUsg: 0, sumWtrUsg: 0, avgWtrUsg: 0 + }; } - } - if(month >= startYm.substring(4, 6) && month <= endYm.substring(4, 6)){ + } + if (month >= startYm.substring(4, 6) && month <= endYm.substring(4, 6)) { cnt++; tot += item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; } - if(rainEffectDay<0) { // 청천시 + if (rainEffectDay < 0) { // 청천시 if (typeof monthData[monthIdx] === 'object') { monthData[monthIdx].sunnyInflAmnt += Number(item2.inflAmnt); // 청천 유입량 합 monthData[monthIdx].sunnyCnt++; - }else { - monthData[monthIdx] = {cnt:1, sum:Number(item2.inflAmnt), yr:item2.msrDt.substring(0,4) - , usage:0, sunnyCnt:0, sunnyInflAmnt:0, sunnyAvgInflAmnt: 0, cntWtrUsg:0, sumWtrUsg:0, avgWtrUsg:0}; + } else { + monthData[monthIdx] = { + cnt: 1, sum: Number(item2.inflAmnt), yr: item2.msrDt.substring(0, 4) + , usage: 0, sunnyCnt: 0, sunnyInflAmnt: 0, sunnyAvgInflAmnt: 0, cntWtrUsg: 0, sumWtrUsg: 0, avgWtrUsg: 0 + }; } - if (month >= startYm.substring(4, 6) && month <= endYm.substring(4, 6)){ + if (month >= startYm.substring(4, 6) && month <= endYm.substring(4, 6)) { sunnyCnt++; sunnyTot += item2.inflAmnt && item2.inflAmnt !== 'x' ? Number(item2.inflAmnt.toString().trim()) : 0; } @@ -630,77 +658,78 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy if (typeof monthData[monthIdx] === 'object') { monthData[monthIdx].sumWtrUsg += Number(item.wtrUsg); monthData[monthIdx].cntWtrUsg += days; - } + } } totWtrUsg += Number(item.wtrUsg); cntWtrUsg += days; }); } - - const tableData:UnderWaterInputModalProps[] = [] - - let maxWaterUsage = 0; - let minWaterUsage = 9999999999; - let avgWaterUsage = 0; - let maxSunnyInflAmnt = 0; - let minSunnyInflAmnt = 9999999999; - let avgSunnyInflAmnt = 0; + + const tableData: UnderWaterInputModalProps[] = [] + + let maxWaterUsage = 0, minWaterUsage = 9999999999, avgWaterUsage = 0; // 물사용량 + let maxSunnyInflAmnt = 0, minSunnyInflAmnt = 9999999999, avgSunnyInflAmnt = 0; // 청천 유입량 + let maxUnderWaterInput = 0, minUnderWaterInput = 9999999999; // 지하수 유입량 monthData.forEach((item, index) => { - item.usage = item.sum/item.cnt; // 일평균 유입량 - item.avgWtrUsg = item.sum/item.cnt * 0.65; // 일평균 물사용량 - if (item.sumWtrUsg > 0 && item.cntWtrUsg > 0) item.avgWtrUsg = item.sumWtrUsg / item.cntWtrUsg; // 일평균 물사용량 + item.usage = item.sum / item.cnt; // 일평균 유입량 + item.avgWtrUsg = item.sum / item.cnt * 0.65; // 일평균 물사용량 : 수용가 물사용량이 없는 경우 + // 일평균 물사용량 : 수용가 물사용량이 있는 경우 + if (item.sumWtrUsg > 0 && item.cntWtrUsg > 0) item.avgWtrUsg = item.sumWtrUsg / item.cntWtrUsg; item.sunnyAvgInflAmnt = item.sunnyInflAmnt / item.sunnyCnt; // 청천 일평균 유입량 + const underWtrInput = Math.round((item.sunnyAvgInflAmnt - (item.avgWtrUsg * OSU_PERCENT)) * 1) / 1; // 지하수 유입량 평균 tableData.push({ - msrDt: item.yr+'-'+(index+1>=10?String(index+1):'0'+(index+1)), - realSewageInput:Math.round(item.sunnyAvgInflAmnt*1) /1, // 청천 실제 하수유입량 - sewageOccur:Math.round(item.avgWtrUsg*OSU_PERCENT * 1) / 1,// 오수발생량 - underWaterInput:Math.round((item.sunnyAvgInflAmnt-(item.avgWtrUsg*OSU_PERCENT)) * 1) / 1, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) + msrDt: item.yr + '-' + (index + 1 >= 10 ? String(index + 1) : '0' + (index + 1)), + realSewageInput: Math.round(item.sunnyAvgInflAmnt * 1) / 1, // 청천 실제 하수유입량 + sewageOccur: Math.round(item.avgWtrUsg * OSU_PERCENT * 1) / 1,// 오수발생량 + underWaterInput: underWtrInput, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) }) - if (item.avgWtrUsg > maxWaterUsage) maxWaterUsage = item.avgWtrUsg; + if (item.avgWtrUsg > maxWaterUsage) maxWaterUsage = item.avgWtrUsg; if (item.sunnyAvgInflAmnt > maxSunnyInflAmnt) maxSunnyInflAmnt = item.sunnyAvgInflAmnt; if (item.avgWtrUsg < minWaterUsage) minWaterUsage = item.avgWtrUsg; if (item.sunnyAvgInflAmnt < minSunnyInflAmnt) minSunnyInflAmnt = item.sunnyAvgInflAmnt; + if (underWtrInput > maxUnderWaterInput) maxUnderWaterInput = underWtrInput; + if (underWtrInput < minUnderWaterInput) minUnderWaterInput = underWtrInput; }); - if(monthData.length == 0){ - maxWaterUsage = 0; minWaterUsage = 0; avgWaterUsage = 0; + if (monthData.length == 0) { + maxWaterUsage = 0; minWaterUsage = 0; avgWaterUsage = 0; maxSunnyInflAmnt = 0; minSunnyInflAmnt = 0; avgSunnyInflAmnt = 0; } - - avgWaterUsage = cnt>0? tot/cnt * 0.65:0; // 일평균 물사용량 - if (totWtrUsg > 0 && cntWtrUsg > 0) avgWaterUsage = totWtrUsg / cntWtrUsg; // 일평균 물사용량 - avgSunnyInflAmnt = (sunnyCnt>0)?sunnyTot/sunnyCnt:0; // 청천 일평균 유입량 - const newData= [ - { - msrDt: '평균', - realSewageInput:Math.round(avgSunnyInflAmnt*1) /1, // 청천시 실제 하수유입량 - sewageOccur:Math.round(avgWaterUsage*OSU_PERCENT * 1) / 1, // 오수발생량 - underWaterInput: Math.round((avgSunnyInflAmnt-(avgWaterUsage*OSU_PERCENT)) * 1) / 1 // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) - //underWaterInput:Math.round((avgWaterUsage-(avgWaterUsage*0.65*0.8)) * 100) / 100, - }, - { - msrDt: '최대', - realSewageInput: Math.round(maxSunnyInflAmnt*1) /1, // 청천시 실제 하수유입량 - sewageOccur: Math.round(maxWaterUsage*OSU_PERCENT * 1) / 1, // 오수발생량 - underWaterInput: Math.round((maxSunnyInflAmnt-(maxWaterUsage*OSU_PERCENT)) * 1) / 1, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) - }, - { - msrDt: '최소', - realSewageInput: Math.round(minSunnyInflAmnt*1) /1, // 청천시 실제 하수유입량 - sewageOccur: Math.round(minWaterUsage*OSU_PERCENT * 1) / 1, // 오수발생량 - underWaterInput: Math.round((minSunnyInflAmnt-(minWaterUsage*OSU_PERCENT)) * 1) / 1, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) - }, - ...tableData + avgWaterUsage = cnt > 0 ? tot / cnt * 0.65 : 0; // 일평균 물사용량 + if (totWtrUsg > 0 && cntWtrUsg > 0) avgWaterUsage = totWtrUsg / cntWtrUsg; // 일평균 물사용량 + avgSunnyInflAmnt = (sunnyCnt > 0) ? sunnyTot / sunnyCnt : 0; // 청천 일평균 유입량 + + const newData = [ + { + msrDt: '평균', + realSewageInput: Math.round(avgSunnyInflAmnt * 1) / 1, // 청천시 실제 하수유입량 + sewageOccur: Math.round(avgWaterUsage * OSU_PERCENT * 1) / 1, // 오수발생량 + underWaterInput: Math.round((avgSunnyInflAmnt - (avgWaterUsage * OSU_PERCENT)) * 1) / 1 // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) + //underWaterInput:Math.round((avgWaterUsage-(avgWaterUsage*0.65*0.8)) * 100) / 100, + }, + { + msrDt: '최대', + realSewageInput: Math.round(maxSunnyInflAmnt * 1) / 1, // 청천시 실제 하수유입량 + sewageOccur: Math.round(maxWaterUsage * OSU_PERCENT * 1) / 1, // 오수발생량 + underWaterInput: maxUnderWaterInput, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) + }, + { + msrDt: '최소', + realSewageInput: Math.round(minSunnyInflAmnt * 1) / 1, // 청천시 실제 하수유입량 + sewageOccur: Math.round(minWaterUsage * OSU_PERCENT * 1) / 1, // 오수발생량 + underWaterInput: minUnderWaterInput, // 지하수 유입량 : 하수처리장 청천시 실제유입량 - (물사용량 × 오수전환율) + }, + ...tableData ] set((state: UnderWaterType) => ({ ...state, UnderWaterDetail: { ...state.UnderWaterDetail, - gridListUnderWaterDetail:newData + gridListUnderWaterDetail: newData } })) } } -} \ No newline at end of file +} diff --git a/apps/fo/next.config.ts b/apps/fo/next.config.ts index 709fd3cf6..6a892461e 100644 --- a/apps/fo/next.config.ts +++ b/apps/fo/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; -const subDomain = - process.env.NEXT_PUBLIC_PROXY_HOST || "http://localhost:3000"; +const domain = process.env.NEXT_PUBLIC_PROXY_HOST || "http://localhost:3000"; +const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000"; const nextConfig: NextConfig = { output: "standalone", @@ -18,7 +18,7 @@ const nextConfig: NextConfig = { { type: "header", key: "referer", - value: "(.*)/pst(.*)", + value: "(.*)/cms(.*)", }, ], }, @@ -26,19 +26,23 @@ const nextConfig: NextConfig = { afterFiles: [ { source: "/sewerage-service/:path*", - destination: `${subDomain}/sewerage-service/:path*`, + destination: `${domain}/sewerage-service/:path*`, }, { source: "/reuse-service/:path*", - destination: `${subDomain}/reuse-service/:path*`, + destination: `${domain}/reuse-service/:path*`, }, { source: "/operation-service/:path*", - destination: `${subDomain}/operation-service/:path*`, + destination: `${domain}/operation-service/:path*`, }, { source: "/ngii-server/:path*", - destination: `${subDomain}/ngii-server/:path*`, + destination: `${domain}/ngii-server/:path*`, + }, + { + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, }, ], fallback: [], diff --git a/apps/fo/package.json b/apps/fo/package.json index 01b09e75c..6993fe17c 100644 --- a/apps/fo/package.json +++ b/apps/fo/package.json @@ -15,7 +15,7 @@ "@tailwindcss/vite": "^4.0.10", "html2canvas": "^1.4.1", "js-cookie": "^3.0.5", - "next": "15.1.7", + "next": "15.3.8", "select": "^1.1.2", "webpack": "^5.98.0", "zustand": "^5.0.3", diff --git a/apps/fo/public/images/help.png b/apps/fo/public/images/help.png new file mode 100644 index 000000000..e872802e4 Binary files /dev/null and b/apps/fo/public/images/help.png differ diff --git a/apps/fo/public/images/map/layericon/RDL_TSCT_PS500.png b/apps/fo/public/images/map/layericon/RDL_TSCT_PS500.png new file mode 100644 index 000000000..b82e2ff89 Binary files /dev/null and b/apps/fo/public/images/map/layericon/RDL_TSCT_PS500.png differ diff --git a/apps/fo/src/entities/auth/index.ts b/apps/fo/src/entities/auth/index.ts index 9dc9dd368..97e142225 100644 --- a/apps/fo/src/entities/auth/index.ts +++ b/apps/fo/src/entities/auth/index.ts @@ -1,2 +1,2 @@ export { LoginApi } from "./api/login-service"; -//export { AuthWrapper } from "./lib/auth-wrapper"; +export { AuthWrapper } from "./lib/auth-wrapper"; diff --git a/apps/fo/src/entities/auth/lib/auth-wrapper.tsx b/apps/fo/src/entities/auth/lib/auth-wrapper.tsx index d3ae23177..21fd87180 100644 --- a/apps/fo/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/fo/src/entities/auth/lib/auth-wrapper.tsx @@ -1,22 +1,23 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -//import { usePathname } from "next/navigation"; import { client } from "@/src/shared/api"; import type { IUserInfo } from "@/src/shared/store"; +import { isAtLoginRedirectUrl } from "@common/network"; import { useUserStore, getCookie } from "@/src/shared/lib"; import { ENDPOINT, AUTH_USER_ID } from "@/src/shared/config"; -import { LoginApi } from "../api/login-service"; -const LOGIN_PAGE_URL = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "http://localhost:3001/auth/login"; // 개발 환경 URL +const LOGIN_PAGE_URL = + process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "/auth/login"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); - //const pathname = usePathname(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(LOGIN_PAGE_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -34,24 +35,29 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); - - const hostname = location.hostname; - - // 개발 환경에서는 localhost 도메인 사용 - if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") - router.push("/"); // 개발시 홈으로 돌아가기 - - // 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용 - // router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용 - if (hostname.includes("hasuon.or.kr")) location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트 + setUserInfo(null); + if (isAtLoginRedirectUrl(LOGIN_PAGE_URL)) { + return; } + + // const hostname = window.location.hostname; + + // // 개발 환경에서는 localhost 도메인 사용 + // if ( + // hostname === "localhost" || + // hostname === "127.0.0.1" || + // hostname === "con.mind-one.co.kr" + // ) { + // router.push("/"); // 개발시 홈으로 돌아가기 + // } + + // // 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용 + // // router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용 + // if (hostname.includes("hasuon.or.kr")) + // window.location.assign(LOGIN_PAGE_URL); // 세션 만료 시 CM 로그인 페이지로 리다이렉트 + + // 개발/운영 환경에서 로그인 URL 분기는 환경 변수로 처리 (.env.*) + window.location.assign(LOGIN_PAGE_URL); } })(); }, []); diff --git a/apps/fo/src/shared/providers/prompt-provider.tsx b/apps/fo/src/shared/providers/prompt-provider.tsx index 10338c338..7724d0326 100644 --- a/apps/fo/src/shared/providers/prompt-provider.tsx +++ b/apps/fo/src/shared/providers/prompt-provider.tsx @@ -1,3 +1,4 @@ +"use client"; import { createContext, useState } from "react"; interface PromptContext { diff --git a/apps/sn/.env.local b/apps/sn/.env.local index c83e4fb68..a47c409a7 100644 --- a/apps/sn/.env.local +++ b/apps/sn/.env.local @@ -5,6 +5,7 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_PORT = 3002 NEXT_PUBLIC_PROXY_HOST = http://localhost:3002 +NEXT_PUBLIC_SUB_DOMAIN = http://localhost:13000 NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&layer=korean_map&style=korean&tilematrixset=EPSG:5179&TileMatrix={z}&TileCol={x}&TileRow={y} @@ -13,7 +14,8 @@ NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&ver NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083 NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +# NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = / #NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.222:8883 # NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883 diff --git a/apps/sn/.env.mindone b/apps/sn/.env.mindone index 7010ce026..cea5518db 100644 --- a/apps/sn/.env.mindone +++ b/apps/sn/.env.mindone @@ -5,6 +5,7 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11302 +NEXT_PUBLIC_SUB_DOMAIN = http://con.mind-one.co.kr:13000 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver diff --git a/apps/sn/.env.production b/apps/sn/.env.production index 004fc8701..4a7c15edc 100644 --- a/apps/sn/.env.production +++ b/apps/sn/.env.production @@ -6,13 +6,13 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token #NEXT_PUBLIC_PROXY_HOST = https://devlion.hasuon.or.kr +#NEXT_PUBLIC_SUB_DOMAIN = http://front-sn-feature.iipbs-web.svc.cluster.local:13000 #NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_NGII_URL = http://172.29.143.87 +NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 - +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login diff --git a/apps/sn/AGENTS.md b/apps/sn/AGENTS.md new file mode 100644 index 000000000..18d6c1e38 --- /dev/null +++ b/apps/sn/AGENTS.md @@ -0,0 +1,35 @@ +## Project +This repository contains a Next.js web application. + +Main technologies: +- Next.js +- TypeScript +- pnpm workspace +- turborepo + +이 저장소는 pnpm workspace + Turborepo 기반 monorepo이다. + +- 워크스페이스 범위는 `apps/*`, `apps/common/*`, `packages/*`, `packages/common/*` +- 루트 스크립트는 `turbo run build`, `turbo run dev` 를 사용한다 +- `build` 작업은 의존 패키지의 `build` 를 먼저 수행한다 (`dependsOn: ["^build"]`) +- `dev` 는 캐시하지 않는 지속 실행 작업이다 +- 변경 범위만 선택 실행할 수 있으므로, 전체 빌드보다 `--filter` 사용을 우선한다 + +## Encoding +- UTF-8 +- 한글이 깨지지 않도록 주의할 것 + +## Monorepo structure +apps/ + _template + as + cm + cs + dc + dm + dm_new + fo + sn + st + wr + diff --git a/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts b/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts index d1e777242..7b591b09c 100644 --- a/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts +++ b/apps/sn/app/(map-layout)/map/components/common/codeMapper.ts @@ -87,6 +87,13 @@ export const codeMapper = ({ layerName, data }: CodeMapperProps): any => { if (processedData.manage_no) { processedData.mqlt_cd = processedData.manage_no; } + if (processedData.mesure_spot_se) { + const mesureSpotSe = [{ id: '0', text: '합류식' }, { id: '1', text: '분류식' }]; + const mesureSpotSeItem = mesureSpotSe.find( + code => code.id === processedData.mesure_spot_se.trim() + ); + if (mesureSpotSeItem) processedData.mesure_spot_se = mesureSpotSeItem.text; + } break; default: diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts index 2581ac0fa..d2aed5956 100644 --- a/apps/sn/app/(map-layout)/map/components/common/index.ts +++ b/apps/sn/app/(map-layout)/map/components/common/index.ts @@ -1,5 +1,5 @@ -import MsSwagePpl from "../popupComponent/MsSwagePpl"; -import sewagePump from "../popupComponent/sewagePump"; +import MsSwagePpl from "../popupComponent/MsSwagePpl"; // 하수관로 +import sewagePump from "../popupComponent/sewagePump"; // 하수펌프 import smh from "../popupComponent/smh"; import Ssc from "../popupComponent/Ssc"; import StormWaterPump from "../popupComponent/stormWaterPump"; diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index a16e68013..4e501fb20 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -27,7 +27,8 @@ export const MsSwagePplCol = [ // { id: 'ftre_smbol_cd', title: '지형지물부호코드' }, // { id: 'lyrTypeNm', title: '레이어타입명' }, ] -export const MsSwagePplCol2 = [ +// 하수관로 searchModal 기본정보tab +export const MsSwagePplCol2 = [ { id: 'rowNumber', title: '일련\n번호' }, { id: 'fcltMngNo', title: '시설\n관리번호' }, { id: 'instMngNo', title: '기관\n관리번호' }, @@ -44,13 +45,13 @@ export const MsSwagePplCol2 = [ { id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' }, { id: 'scendDpth', title: '종점깊이\n(m)' }, { id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' }, - // { id: 'vrtcLen', title: '세로길이\n(m)' }, - // { id: 'wdthLen', title: '가로길이\n(m)' }, - // { id: 'fcltyShpCd', title: '시설물형태코드' }, + { id: 'vrtcLen', title: '세로길이\n(m)' }, + { id: 'wdthLen', title: '가로길이\n(m)' }, + { id: 'fcltyShpCd', title: '시설물형태코드' }, // { id: 'fid', title: 'FID' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, // { id: 'mngInstCd', title: '관리기관코드' }, - // { id: 'mqltCd', title: '재질코드' }, + { id: 'mqltCd', title: '재질코드' }, // { id: 'pbadmsEmdCd', title: '행정읍면동코드' }, // { id: 'usgCd', title: '용도코드' }, // { id: 'lyrTypeNm', title: '레이어타입명' }, @@ -69,6 +70,10 @@ export const MsSwagePplExcelCol = { 'tmptMhlMngNo': '시점맨홀 관리번호', 'scendDpth': '종점 깊이(m)', 'scendMhlMngNo': '종점맨홀 관리번호', + 'vrtcLen': '세로길이(m)', + 'wdthLen': '가로길이(m)', + 'fcltyShpCd': '시설물형태코드', + 'mqltCd': '재질코드', }; // 하수관로 용도 코드표 export const MS_SWAGE_PPL_USE_CODES = [ @@ -275,7 +280,80 @@ export const MS_SWAGE_PPL_CODES = [ { val: "MOP977", text: "전주용입상관" }, { val: "MOP978", text: "MD관" }, ]; +// 하수관로 'fcltyShpCd' '시설물형태코드' +export const MS_SWAGE_PPL_SHAPE = [ + { val: "FOR000 ", text: "미분류" }, + { val: "FOR001 ", text: "원형" }, + { val: "FOR002 ", text: "원추형" }, + { val: "FOR003 ", text: "원통형" }, + { val: "FOR004 ", text: "구형" }, + { val: "FOR005 ", text: "타원형" }, + { val: "FOR006 ", text: "난원" }, + { val: "FOR007 ", text: "반원형" }, + { val: "FOR010 ", text: "피라미드형" }, + { val: "FOR011 ", text: "사면체형" }, + { val: "FOR012 ", text: "기둥,축" }, + { val: "FOR013 ", text: "돛대,활대처럼 둥글고 긴 원재" }, + { val: "FOR014 ", text: "아치형" }, + { val: "FOR015 ", text: "다중아치형" }, + { val: "FOR016 ", text: "돔형" }, + { val: "FOR017 ", text: "고형체형" }, + + { val: "FOR020 ", text: "사각형" }, + { val: "FOR021 ", text: "평방형" }, + { val: "FOR022 ", text: "다이아몬드형" }, + { val: "FOR023 ", text: "사다리꼴" }, + { val: "FOR024 ", text: "각형" }, + { val: "FOR025 ", text: "마제형관" }, + { val: "FOR026 ", text: "태형" }, + + { val: "FOR030 ", text: "A형" }, + { val: "FOR031 ", text: "H형" }, + { val: "FOR032 ", text: "I형" }, + { val: "FOR033 ", text: "Y형" }, + { val: "FOR034 ", text: "T형" }, + + { val: "FOR050 ", text: "불규칙한 형태" }, + + { val: "FOR900 ", text: "계란형" }, + { val: "FOR901 ", text: "구(사각)형" }, + { val: "FOR902 ", text: "높이조절철개형" }, + { val: "FOR903 ", text: "박스형(RCB)" }, + { val: "FOR904 ", text: "반월개거" }, + { val: "FOR905 ", text: "반월개거(측구)" }, + { val: "FOR906 ", text: "벽돌조" }, + { val: "FOR907 ", text: "사각철판" }, + { val: "FOR908 ", text: "석축개거" }, + { val: "FOR909 ", text: "석축개거(측구)" }, + { val: "FOR910 ", text: "승수로" }, + { val: "FOR911 ", text: "옹벽개거" }, + { val: "FOR912 ", text: "옹벽개거(측구)" }, + { val: "FOR913 ", text: "이단" }, + { val: "FOR914 ", text: "이중침전형" }, + { val: "FOR915 ", text: "자연개거" }, + { val: "FOR916 ", text: "자연개거(측구)" }, + { val: "FOR917 ", text: "조립식" }, + { val: "FOR918 ", text: "지상관" }, + { val: "FOR919 ", text: "지상단구" }, + { val: "FOR920 ", text: "지상쌍구" }, + { val: "FOR921 ", text: "지하단구" }, + { val: "FOR922 ", text: "지하쌍구" }, + { val: "FOR923 ", text: "직사각형" }, + { val: "FOR924 ", text: "타설식" }, + { val: "FOR925 ", text: "토류형" }, + { val: "FOR926 ", text: "흄관원형" }, + { val: "FOR927 ", text: "L형측구" }, + { val: "FOR928 ", text: "LO형측구" }, + { val: "FOR929 ", text: "LU형측구" }, + { val: "FOR930 ", text: "PVC이중벽관" }, + { val: "FOR931 ", text: "RCB 맨홀" }, + { val: "FOR932 ", text: "U형" }, + { val: "FOR933 ", text: "U형측구(미복개)" }, + { val: "FOR934 ", text: "U형측구(복개)" }, + + { val: "FOR999 ", text: "기타" } +]; // 하수처리시설 export const SwtrmtFcltCol = [ { id: 'swtrmt_fclt_nm', title: '하수처리시설명' }, @@ -415,8 +493,8 @@ export const MsSwageMhlCol = [ { id: 'wdth_len', title: '가로길이(m)' }, { id: 'vrtc_len', title: '세로길이(m)' }, // { id: 'ang_dir', title: '표고' }, - { id: 'altd_vl', title: '표고(EL.m)' }, - { id: 'dpthl_vl', title: '심도(m)' }, + { id: 'altd_vl', title: '지표고(EL.m)' }, + { id: 'dpthl_vl', title: '맨홀저고(EL.m)' }, // { id: 'inst_mng_no', title: '기관관리번호' } ]; export const MsSwageMhlCol2 = [ @@ -440,8 +518,8 @@ export const MsSwageMhlCol2 = [ { id: 'wdthLen', title: '가로길이(m)' }, { id: 'vrtcLen', title: '세로길이(m)' }, // { id: 'angDir', title: '표고' }, - { id: 'altdVl', title: '표고(EL.m)' }, - { id: 'dpthlVl', title: '심도(m)' }, + { id: 'altdVl', title: '지표고(EL.m)' }, + { id: 'dpthlVl', title: '맨홀저고(EL.m)' }, // { id: 'instMngNo', title: '기관관리번호' } ]; export const MsSwageMhlExcelCol = { @@ -453,8 +531,8 @@ export const MsSwageMhlExcelCol = { 'swageMhlDiamVl': '하수맨홀구경(mm)', 'wdthLen': '가로길이(m)', 'vrtcLen': '세로길이(m)', - 'altdVl': '표고(EL.m)', - 'dpthlVl': '심도(m)' + 'altdVl': '지표고(EL.m)', + 'dpthlVl': '맨홀저고(EL.m)' }; // 하수맨홀 뚜껑재질 코드표 export const MS_SWAGE_MHL_CODES = [ @@ -686,7 +764,7 @@ export const untrtSwgRainCol1 = [ // 중점우수토실 측정지점 시설현황 export const untrtSwgRainCol2 = [ - { id: 'flwmtr_mesure_tp_etc', title: '구분' }, + { id: 'mesure_spot_se', title: '구분' }, // 유량계측정방식 기타 { id: 'discharge_pipe_spec', title: '방류관로 규격(m)' }, { id: 'swr_ppl_eqp_ar', title: '배수구역 면적(m²)' }, { id: 'swr_trt_popl', title: '하수처리 인구(인)' }, @@ -722,7 +800,7 @@ export const untrtSwgRainCol4 = [ export const untrtSwgRainCol5 = [ { id: 'odr', title: '회차' }, { id: 'mesureDt', title: '측정일자' }, - { id: 'mesureQy', title: '수량(m)' }, + { id: 'mesureQy', title: '수량(m³)' }, { id: 'mesureBod', title: 'BOD(mg/L)' }, { id: 'mesureToc', title: 'TOC(mg/L)' }, { id: 'mesureLoad', title: '오염부하량(kg)' } @@ -767,4 +845,4 @@ export const numberFields = [ 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc', 'bizAmt', 'count', 'chmbr_xcnts', 'chmbr_ydnts' -]; \ No newline at end of file +]; diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx index 3ce6b30a7..f908fe98b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx @@ -100,15 +100,20 @@ const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: { const processedDataArray: any[] = result.content.map((data: any) => { // 🎯 formatDisplayData로 기본 포맷팅 적용 const formattedItem = formatDisplayData(data); - + const flwmtrInstTp = data.flwmtrInstTp === 'R' ? '이동식' : data.flwmtrInstTp === 'S' ? '고정식' : '-'; + const flwmtrMesureTp = data.flwmtrMesureTp === 'R' ? '레이더' : data.flwmtrMesureTp === 'P' ? '플륨' + : data.flwmtrMesureTp === 'U' ? '초음파' : data.flwmtrMesureTp === 'E' ? '기타' : '-'; + const flwmtrCombined = `${flwmtrInstTp}, ${flwmtrMesureTp}`; // 🎯 추가 데이터 조합 return { ...formattedItem, - flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`, - mesureBeginDt: data.mesureBeginDt ? formatNoYearNoSec(data.mesureBeginDt) : '-', - mesureEndDt: data.mesureEndDt ? formatNoYearNoSec(data.mesureEndDt) : '-', - qltwtrBeginDt: data.qltwtrBeginDt ? formatNoYearNoSec(data.qltwtrBeginDt) : '-', - qltwtrEndDt: data.qltwtrEndDt ? formatNoYearNoSec(data.qltwtrEndDt) : '-', + rainInfo: `${data.rainInfo === '0' ? '강우량계' : data.observatory || '-'}`, // 강우정보 0: 강우량계,1: 관측기관 이면 OBSERVATORY 이값사용 + mesureIntrvl: `${data.mesureIntrvlGbn === '1' ? '1분' : (data.mesureIntrvlGbn === '0' ? data.mesureIntrvl + '분' : '-')}`, // 측정간격 + flwmtrCombined: `${flwmtrCombined} / ${data.flwmtrKnd || '-'}`, // 유량계종류 및 모델명 + mesureBeginDt: data.mesureBeginDt ? formatNoYearNoSec(data.mesureBeginDt) : '-', // 수량 측정시작일 + mesureEndDt: data.mesureEndDt ? formatNoYearNoSec(data.mesureEndDt) : '-', // 수량 측정종료일 + qltwtrBeginDt: data.qltwtrBeginDt ? formatNoYearNoSec(data.qltwtrBeginDt) : '-', // 수질 측정시작일(채수시작일) + qltwtrEndDt: data.qltwtrEndDt ? formatNoYearNoSec(data.qltwtrEndDt) : '-', // 수질 측정종료일(채수종료일) }; }); setRainMeasureData(processedDataArray); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx index 9f481a2b9..3836b87f6 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx @@ -1,268 +1,279 @@ 'use client' -import { CHART_FORM, type ChartConfig, MainChart, UiModal } from "@common/business_components"; + +import React, { useEffect, useMemo, useState } from 'react'; +import moment from 'moment'; + +import { + CHART_FORM, + type ChartConfig, + MainChart, + UiModal, +} from '@common/business_components'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; -import React, { useEffect, useState } from 'react'; -import moment from "moment"; import type { TInftwtAnls } from './aplcnYms'; import * as fn from './aplcnYms'; -import { useInfilChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어 +import { useInfilChartModalStore } from '../chartModalStore'; + type ModalOpenProp = { - pplMngNo?: string, // 관로관리번호 -} -type DataType = { label: string; val: number }; + pplMngNo?: string; // 관로관리번호 +}; -const InfilChart = ({ pplMngNo }: ModalOpenProp) => { - const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useInfilChartModalStore(); // 모달 상태 관리 스토어 - const [popupOpen, setPopupOpen] = React.useState(false); // 조회조건팝업 - const buttonRef = React.useRef<HTMLButtonElement>(null); // 버튼 참조 - const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const [selectedAplcnYm, setSelectedAplcnYm] = useState(""); +type DataType = { + label: string; + val: number; +}; + +const INITIAL_ANALYSIS_OPTIONS = [{ val: 0, text: '선택' }]; + +const InfilChart = ({ pplMngNo = '' }: ModalOpenProp) => { + const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useInfilChartModalStore(); + + const [selectedAplcnYm, setSelectedAplcnYm] = useState(''); const [inftwtAnlss, setInftwtAnlss] = useState<TInftwtAnls[]>([]); - const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 침입수분석정보 options - const [inftwtAnls, setInftwtAnls] = useState<TInftwtAnls | null>(null); // 침입수분석정보 - const [selectedInftwtAnls, setSelectedInftwtAnls] = useState<number>(0); // 침입수분석 정보 선택시 사용 + const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>( + INITIAL_ANALYSIS_OPTIONS, + ); + const [inftwtAnls, setInftwtAnls] = useState<TInftwtAnls | null>(null); + const [selectedInftwtAnls, setSelectedInftwtAnls] = useState<number>(0); const [chartData, setChartData] = useState<{ chartData1: DataType[]; chartData2: DataType[] }>({ - chartData1: [], chartData2: [], + chartData1: [], + chartData2: [], }); - const [inftwtQty, setInftwtQty] = useState<string>("0"); // 침입수량 - const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); // 상수사용월 데이터 가져오기 + const [inftwtQty, setInftwtQty] = useState<string>('0'); - pplMngNo = "L" + pplMngNo; // inp파일에 하수관로와 맨홀의 번호가 같으면 분석 오류가 발행하여 - const openPopup = () => { - if (buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 - setPopupPosition({ - top: rect.bottom + window.scrollY, // 버튼 아래쪽 - left: rect.left + window.scrollX, // 버튼 왼쪽 - }); - } - setPopupOpen(true); // 팝업 열기 - }; + const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); + const normalizedPplMngNo = pplMngNo ? `L${pplMngNo}` : ''; const chartForm = CHART_FORM.LINE; const chartConfig = { - chartData1: { label: '순오수량', color: "red", }, - chartData2: { label: '실측하수량', color: "orange", } + chartData1: { label: '순오수량', color: 'red' }, + chartData2: { label: '실측하수량', color: 'orange' }, } as ChartConfig; + const hasChartData = useMemo(() => { + return chartData.chartData1.length > 0 || chartData.chartData2.length > 0; + }, [chartData]); + + const resetState = () => { + setSelectedAplcnYm(''); + setInftwtAnlss([]); + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); + setInftwtAnls(null); + setSelectedInftwtAnls(0); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + }; + useEffect(() => { - if (isModalOpen === false) { - setPopupOpen(true); // 모달이 닫힐 때 조회조건팝업은 열어두기 - setSelectedAplcnYm(""); // 상수사용월 초기화 - setInftwtAnlss([]); // 침입수분석 초기화 - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); // 침입수분석정보 초기화 - setInftwtAnls(null); // 침입수분석정보 초기화 - setSelectedInftwtAnls(0); // 침입수분석 일련번호 초기화 - setChartData({ chartData1: [], chartData2: [], }); // 차트 데이터 초기화 - setInftwtQty("0"); // 침입수량 초기화 + if (!isModalOpen) { + resetState(); // 모달이 닫힐 때 리셋 } }, [isModalOpen]); - // 상수사용월 변경 핸들러 const handleAplcnYmChange = async (val: string | number) => { const selectedValue = String(val); setSelectedAplcnYm(selectedValue); - - // 즉시 관련 상태 초기화 + setInftwtAnlss([]); setInftwtAnls(null); + setSelectedInftwtAnls(0); + setInftwtQty('0'); + setChartData({ chartData1: [], chartData2: [] }); - // 빈 값이면 초기화만 하고 종료 - if (!selectedValue || selectedValue === "0" || selectedValue === "") { - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); + if (!selectedValue || selectedValue === '0') { + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); return; } - // API 호출 try { const data = await fn.selectInftwtAnls(selectedValue); - setInftwtAnlss(data ?? []); - setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(data)); + const list = data ?? []; + setInftwtAnlss(list); + setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(list)); } catch (error) { - console.error("selectInftwtAnls 에러:", error); + console.error('selectInftwtAnls 에러:', error); setInftwtAnlss([]); - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); } - setSelectedInftwtAnls(0); }; - // 분석정보 변경 핸들러 const handleInftwtAnlsSnChange = (val: string | number) => { const selectedValue = Number(val); - setSelectedInftwtAnls(selectedValue); - // 선택된 침입수분석 정보에 대한 처리 로직 + setSelectedInftwtAnls(selectedValue); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + if (selectedValue === 0) { setInftwtAnls(null); return; } - const selectedAnls = inftwtAnlss.find(anls => anls.inftwtAnlsSn === selectedValue); - if (selectedAnls) { - setInftwtAnls(selectedAnls); + const selectedAnls = inftwtAnlss.find((anls) => anls.inftwtAnlsSn === selectedValue) ?? null; + setInftwtAnls(selectedAnls); + }; + + const infilRsltChart = async () => { + if (!selectedAplcnYm || selectedInftwtAnls === 0 || !normalizedPplMngNo) { + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + return; + } + + try { + const [inftwtAnlsRslt, oilq, qty] = await Promise.all([ + fn.selectInftwtAnlsRslt(selectedInftwtAnls, normalizedPplMngNo), + fn.selectOilq(selectedInftwtAnls, normalizedPplMngNo).catch((err) => { + console.error('selectOilq 에러:', err); + return []; + }), + fn.selectPplInftwtQty(selectedInftwtAnls, normalizedPplMngNo).catch((err) => { + console.error('selectPplInftwtQty 에러:', err); + return 0; + }), + ]); + + setChartData({ + chartData1: fn.getChartDataInftwtAnlsRslt(inftwtAnlsRslt ?? []), + chartData2: fn.getChartDataOilq(oilq ?? []), + }); + + const normalizedQty = Number(qty ?? 0); + setInftwtQty(normalizedQty.toLocaleString('ko-KR')); + } catch (error) { + console.error('infilRsltChart 에러:', error); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); } }; - const closePopup = () => { // 팝업 닫기 - setPopupOpen(false); - } - const infilRsltChart = () => { // 그래프 & 팝업 닫기 + const renderInfoInput = (label: string, value?: string | number | null) => ( + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto text-[14px] font-semibold text-[#5c6470] whitespace-nowrap">{label}</Label> + <Input + className="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white px-3 text-xl md:text-xl text-[#2b3440]" + placeholder="" + value={value ?? ''} + readOnly + /> + </div> + ); - fn.selectInftwtAnlsRslt(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수분석 결과 조회 - const rsltData = fn.getChartDataInftwtAnlsRslt(data ?? []); - setChartData(prev => ({ - ...prev, - chartData1: rsltData // 침입수분석결과 - })); - }); - fn.selectOilq(selectedInftwtAnls, pplMngNo).then((data) => {// 유량 조회 - const rsltData = fn.getChartDataOilq(data ?? []); - setChartData(prev => ({ - ...prev, - chartData2: rsltData // 유량 - })); - }).catch((err) => { - console.error("selectOilq 에러:", err); - setChartData(prev => ({ - ...prev, - chartData2: [] // 유량 데이터 초기화 - })); - }); - fn.selectPplInftwtQty(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수량 조회 - if (data > 0) { - setInftwtQty(data.toLocaleString('ko-KR')); - } - }); - setPopupOpen(false); - }; + const handleReset = () => { + resetState(); + } const renderBody = () => ( - <> - <div className="text-black text-3xl font-bold mb-5">침입수분석결과</div> - - <div className='grid grid-cols-[1fr_3fr] gap-4'> - <div className="h-[90%] grid grid-rows-4 gap-4 mb-4"> - <Button className="bg-white text-white w-[130px] font-bold px-4 mt-2 py-2" onClick={openPopup}> - 분석 입력현황 조회 - </Button> - {/* 상수사용량 적용연월 */} - <div className="bg-white px-4 py-2 "> - <span className="text-2xl text-red-600 font-bold">● 상수사용량 적용연월</span> - <div className="flex items-center mt-2"> - <span className="ml-5 text-black text-xl font-bold"> - {(selectedAplcnYm) ? moment(selectedAplcnYm, "YYYYMM").format("YYYY-MM") : ""} - </span> - </div> - </div> - {/* 침입수량 분석결과 */} - <div className="bg-white px-4 py-2 "> - <span className="text-2xl text-red-600 font-bold">● 침입수량 산정결과</span> - <div className="flex items-center mt-2"> - <span className="ml-5 text-black text-xl">침입수량 : </span> - <span className="w-[45px] inline-block text-right text-black text-xl font-bold">{inftwtQty}</span> - <span className="ml-2 text-black text-xl">m³/d</span> - </div> - </div> - </div> - {/* 차트 */} - <div className="h-[90%] bg-white text-3xl"> - <MainChart chartConfig={chartConfig} chartForm={chartForm} chartData={chartData} chartTitle={'침입수량 그래프'} - yFieldText2="" yFieldText="유량(m³/h)"/> - </div> + <div className="text-black"> + <div className="mb-4 flex items-start justify-between"> + <div className="text-[18px] font-semibold text-[#111827]" /> </div> - {popupOpen && ( - <div - className="absolute bg-gray-100 border border-gray-300 w-[440px] shadow-lg p-6 rounded text-2xl" - style={{ - top: popupPosition.top, - left: popupPosition.left, - zIndex: 1000, - }} - > - <div className="font-bold mb-6 text-3xl">침입수분석 입력현황</div> - <div className='flex flex-col'> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">상수사용월:</Label> - <SelectBox - className="w-[220px] mr-8 z-[51] mb-2" - selectClass="rounded-lg bg-white" - label='' - selectArray={aplcnYms} - value={selectedAplcnYm} - onSelectValue={handleAplcnYmChange} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">분석정보:</Label> - <SelectBox - className="w-[220px] mr-8 z-[51] mb-2" - selectClass="rounded-lg bg-white" - label='' - selectArray={inftwtAnlsInfos} - value={selectedInftwtAnls} - onSelectValue={handleInftwtAnlsSnChange} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">오수전환율:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={inftwtAnls?.drtwSwtcRt ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">시간패턴 파일명:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={inftwtAnls?.atflHrPttnNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">수용가-맨홀 매칭:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={inftwtAnls?.atflMpngNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">관망도 파일명:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={inftwtAnls?.atflInpNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">유량 파일명:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={inftwtAnls?.atflOilqNm ?? ""} - readOnly={true} - /> + + <div className="grid min-h-[68vh] grid-cols-[minmax(0,38%)_minmax(0,62%)] gap-5"> + <section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5"> + <h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black"> + 침입수 분석 선택 조건 + </h3> + + <div className="rounded-[6px] border border-[#d9dde3] bg-[#eff1f4] px-4 py-6"> + <div className="space-y-3"> + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto text-[14px] font-semibold text-[#5c6470] whitespace-nowrap"> + 상수사용월: + </Label> + <SelectBox + className="w-full" + selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl" + label="" + selectArray={aplcnYms} + value={selectedAplcnYm} + onSelectValue={handleAplcnYmChange} + /> + </div> + + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto text-[14px] font-semibold text-[#5c6470] whitespace-nowrap"> + 분석정보: + </Label> + <SelectBox + className="w-full" + selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl" + label="" + selectArray={inftwtAnlsInfos} + value={selectedInftwtAnls} + onSelectValue={handleInftwtAnlsSnChange} + /> + </div> + + {renderInfoInput('오수전환율:', inftwtAnls?.drtwSwtcRt ?? '')} + {renderInfoInput('시간패턴 파일명:', inftwtAnls?.atflHrPttnNm ?? '')} + {renderInfoInput('수용가-맨홀 매칭:', inftwtAnls?.atflMpngNm ?? '')} + {renderInfoInput('관망도 파일명:', inftwtAnls?.atflInpNm ?? '')} + {renderInfoInput('유량 파일명:', inftwtAnls?.atflOilqNm ?? '')} </div> </div> - <div className="flex justify-end mt-4 space-x-2"> - <button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}> - 닫기 - </button> - <button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={infilRsltChart}> - 선택조건 적용 - </button> + + <div className="absolute inset-x-0 bottom-0 mb-[40px] flex justify-center"> + <Button size="lg" onClick={infilRsltChart} > + 조회 + </Button>   + <Button size="lg" color="gray" onClick={handleReset} > + 초기화 + </Button> </div> - </div>)} - {/* </Modal > */} - </> - ) + </section> + + <section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5"> + <h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black">침입수 분석 결과</h3> + + <div className="mb-6 grid grid-cols-2 gap-5"> + <div className="flex min-h-[98px] items-center justify-between rounded-[14px] border border-[#4f88c6] bg-white px-6 py-4"> + <div className="flex items-center gap-2 text-[16px] font-extrabold text-[#ef4444]"> + {/* <span className="text-[14px] leading-none">●</span> */} + <span>상수사용량 적용연월</span> + </div> + <div className="text-[20px] font-extrabold text-[#222]"> + {selectedAplcnYm ? moment(selectedAplcnYm, 'YYYYMM').format('YYYY-MM') : '-'} + </div> + </div> + + <div className="flex min-h-[98px] items-center justify-between rounded-[14px] border border-[#4f88c6] bg-white px-6 py-4"> + <div className="flex items-center gap-2 text-[16px] font-extrabold text-[#ef4444]"> + {/* <span className="text-[14px] leading-none">●</span> */} + <span>침입수량 산정결과</span> + </div> + <div className="grid grid-cols-[1fr_auto] items-center gap-7 text-[#222]"> + <span className="text-[16px] text-[#666]">침입수량</span> + <div className='w-[66px] flex items-end justify-start gap-2'> + <span className="text-[20px] font-extrabold">{inftwtQty}</span> + <span className="pb-[2px] text-[16px]">m³/d</span> + </div> + </div> + </div> + </div> + + <div className="rounded-[10px] bg-white px-2 pb-2 pt-3"> + {hasChartData ? ( + <MainChart + chartConfig={chartConfig} + chartForm={chartForm} + chartData={chartData} + chartTitle={'침입수량 그래프'} + yFieldText="유량(m³/h)" + yFieldText2="" + /> + ) : ( + <div className="flex h-[464px] items-center justify-center text-[18px] text-[#2c2f33]"> + 데이터가 없습니다. + </div> + )} + </div> + </section> + </div> + </div> + ); return ( <UiModal @@ -270,17 +281,17 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - min-h-[50vh] - w-[55vw] - max-w-none - max-h-none - overflow-visible - z-[50] - `} + w-[78vw] + max-w-none + min-w-[1000px] + min-h-[72vh] + overflow-visible + z-[50] + `} body={renderBody()} - noFooterYn='Y' + noFooterYn="Y" /> - ) -} + ); +}; -export default React.memo(InfilChart); \ No newline at end of file +export default React.memo(InfilChart); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx index 39617eb21..aa622be1b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx @@ -1,282 +1,339 @@ 'use client' -import { CHART_FORM, type ChartConfig, MainChart, UiModal } from "@common/business_components"; + +import React, { useEffect, useMemo, useState } from 'react'; +import moment from 'moment'; + +import { + CHART_FORM, + type ChartConfig, + MainChart, + UiModal, +} from '@common/business_components'; import { SelectBox } from '@common/business_components/ui'; import { Button, Input, Label } from '@common/components'; -import React, { useEffect, useState } from 'react'; -import moment from "moment"; import type { TRdiiAnls } from './aplcnYms'; import * as fn from './aplcnYms'; -import { useRdiiChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어 +import { useRdiiChartModalStore } from '../chartModalStore'; type ModalOpenProp = { - pplMngNo: string, // 관로관리번호 -} -// 차트 -type lineBarChartData = { - label : string; - lineVal0 : number; - barVal0 : number; -} + pplMngNo?: string; // 관로관리번호 +}; +type LineBarChartData = { + label: string; + lineVal0: number; + barVal0: number; +}; -const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { - const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useRdiiChartModalStore(); // 모달 상태 관리 스토어 - const [popupOpen, setPopupOpen] = React.useState(false); // 조회조건팝업 - const buttonRef = React.useRef<HTMLButtonElement>(null); // 버튼 참조 - const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const [aplcnYm, setAplcnYm] = useState(""); +const INITIAL_ANALYSIS_OPTIONS = [{ val: 0, text: '선택' }]; + +const RdiiChart = ({ pplMngNo = '' }: ModalOpenProp) => { + const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useRdiiChartModalStore(); + + const [aplcnYm, setAplcnYm] = useState(''); const [rdiiAnlss, setRdiiAnlss] = useState<TRdiiAnls[]>([]); - const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options + const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>( + INITIAL_ANALYSIS_OPTIONS, + ); const [rdiiAnls, setRdiiAnls] = useState<TRdiiAnls | null>(null); // 강우유입수분석정보 - const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState<number>(0); // 강우유입수분석 정보 선택시 사용 - const [chartData, setChartData] = useState<lineBarChartData[]>([]); - const [rdiiQty, setRdiiQty] = useState<string>("0"); // 강우유입수량 - const [rainTotal, setRainTotal] = useState<string>("0"); // 적용강우량 - const [totalVolume, setTotalVolume] = useState<string>("0"); // 총체적 m³/mm + const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState<number>(0);// 강우유입수분석 정보 선택시 사용 + const [chartData, setChartData] = useState<LineBarChartData[]>([]); + const [rdiiQty, setRdiiQty] = useState<string>('0'); // 강우유입수량 + const [rainTotal, setRainTotal] = useState<string>('0'); // 적용강우량 + const [totalVolume, setTotalVolume] = useState<string>('0');// 총체적 m³/mm + const aplcnYms = fn.useAplcnYms('rdiiAnlsRslt'); // 상수사용월 데이터 가져오기 - pplMngNo = "L"+pplMngNo; // "L13101003"; - - const openPopup = () => { - if (buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 - setPopupPosition({ - top: rect.bottom + window.scrollY, // 버튼 아래쪽 - left: rect.left + window.scrollX, // 버튼 왼쪽 - }); - } - setPopupOpen(true); // 팝업 열기 - }; + const normalizedPplMngNo = pplMngNo ? `L${pplMngNo}` : ''; const chartForm = CHART_FORM.LINEBAR2; const chartConfig = { - barVal0: { + barVal0: { label: '강수량', - color: "#33AAFF", + color: '#33AAFF', }, lineVal0: { label: 'RDII', - color: "#FF0000", + color: '#FF0000', }, } as ChartConfig; + const hasChartData = useMemo(() => chartData.length > 0, [chartData]); + + const resetState = () => { + setAplcnYm(''); // 상수사용월 초기화 + setRdiiAnlss([]); // 강우유입수분석 초기화 + setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS); // 강우유입수분석정보 초기화 + setRdiiAnls(null); // 강우유입수분석정보 초기화 + setRainInfwtAnlsSn(0); // 강우유입수분석 일련번호 초기화 + setChartData([]); + setRdiiQty('0'); // 강우유입수량 초기화 + setRainTotal('0');// 적용강우량 초기화 + setTotalVolume('0');// 총체적 m³/mm 초기화 + }; + useEffect(() => { - if (isModalOpen === false) { - setPopupOpen(true); // 모달이 닫힐 때 팝업은 - setAplcnYm(""); // 상수사용월 초기화 - setRdiiAnlss([]); // 강우유입수분석 초기화 - setRdiiAnlsInfos([{ val: 0, text: '선택' }]); // 강우유입수분석정보 초기화 - setRdiiAnls(null); // 강우유입수분석정보 초기화 - setRainInfwtAnlsSn(0); // 강우유입수분석 일련번호 초기화 - setChartData([]); // 차트 데이터 초기화 - setRdiiQty("0"); // 강우유입수량 초기화 - setRainTotal("0"); // 적용강우량 초기화 - setTotalVolume("0"); // 총체적 m³/mm 초기화 + if (!isModalOpen) { + resetState(); } }, [isModalOpen]); - // 상수사용월 변경시 분석 정보 리스트 가져오기 - useEffect(() => { - // aplcnYm이 비어있거나 "0"이면 API 호출하지 않음 - if (!aplcnYm || aplcnYm === "0" || aplcnYm === "") { - setRainInfwtAnlsSn(0); // 초기화 - setRdiiAnlsInfos([]); + const handleAplcnYmChange = async (val: string | number) => { + const selectedValue = String(val); + + setAplcnYm(selectedValue); + setRdiiAnlss([]); + setRdiiAnls(null); + setRainInfwtAnlsSn(0); + setChartData([]); + setRdiiQty('0'); + setRainTotal('0'); + setTotalVolume('0'); + + if (!selectedValue || selectedValue === '0') { + setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS); return; } - fn.selectRdiiAnls(aplcnYm) - .then((data) => { - setRdiiAnlss(data ?? []); // 선택한 월의 분석성공한 RDII분석데이터[] - setRdiiAnlsInfos(fn.getOptionsRdiiAnlsInfo(data)); - setRdiiAnls(null); // 오수전환율 이후 초기화 - setRainInfwtAnlsSn(0); // 초기화 - }) - .catch((error) => { - console.error("selectrdiiAnls 에러:", error); - setRdiiAnlss([]); - setRdiiAnlsInfos([]); - }); - }, [aplcnYm]); - // 값 표시 - useEffect(() => { - // 선택된 강우유입수분석 정보에 대한 처리 로직 - if (rainInfwtAnlsSn === 0) return; - const selectedAnls = rdiiAnlss.find(anls => anls.rainInfwtAnlsSn === rainInfwtAnlsSn); - if (selectedAnls) { - setRdiiAnls(selectedAnls); + try { + const data = await fn.selectRdiiAnls(selectedValue); // 상수사용월 변경시 분석 정보 리스트 가져오기 + const list = data ?? []; + setRdiiAnlss(list); + setRdiiAnlsInfos(fn.getOptionsRdiiAnlsInfo(list)); + } catch (error) { + console.error('selectRdiiAnls 에러:', error); + setRdiiAnlss([]); + setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS); } - }, [rainInfwtAnlsSn, rdiiAnlss]); - - const closePopup = () => { // 팝업 닫기 - setPopupOpen(false); - } - const rdiiRsltChart = () => { // 그래프 & 팝업 닫기 - fetchFilteredData(); - fn.selectPplRdiiQty(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수량 조회 - setRdiiQty(Math.round(data?.rdiih*24).toLocaleString('ko-KR')); // cmd: m³/d 단위로 변환 - if (data?.raintotal && data?.rainTotal !== 0) { - setRainTotal(data?.raintotal.toLocaleString('ko-KR')); // 총강우량mm - setTotalVolume(Math.round(data?.rdiih * data?.sustaintime / data?.raintotal).toLocaleString('ko-KR')); // 총체적 m³/mm - } - }); - setPopupOpen(false); }; - const fetchFilteredData: () => Promise<void> = async () => { - const rsltData = await fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, pplMngNo); - const rsltDataformatted = fn.getChartDataRdiiAnlsRslt(rsltData ?? []); // format 변경 - const rainData = await fn.selectRainData(rainInfwtAnlsSn); - const rainDataformatted = fn.getChartDataRainData(rainData ?? []); // format 변경 - const rainMap = new Map(rainDataformatted.map(item => [item.label, item.barVal0])); + const handleRdiiAnlsSnChange = (val: string | number) => { + const selectedValue = Number(val); + + setRainInfwtAnlsSn(selectedValue); + setChartData([]); + setRdiiQty('0'); + setRainTotal('0'); + setTotalVolume('0'); + + if (selectedValue === 0) { + setRdiiAnls(null); + return; + } + + const selectedAnls = rdiiAnlss.find((anls) => anls.rainInfwtAnlsSn === selectedValue) ?? null; + setRdiiAnls(selectedAnls); + }; + + const fetchFilteredData = async () => { + if (!rainInfwtAnlsSn || !normalizedPplMngNo) { + setChartData([]); + setRdiiQty('0'); + setRainTotal('0'); + setTotalVolume('0'); + return; + } + + try { + const [rsltData, rainData, qtyData] = await Promise.all([ + fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, normalizedPplMngNo), + fn.selectRainData(rainInfwtAnlsSn), + fn.selectPplRdiiQty(rainInfwtAnlsSn, normalizedPplMngNo), + ]); + + const rsltDataFormatted = fn.getChartDataRdiiAnlsRslt(rsltData ?? []); + const rainDataFormatted = fn.getChartDataRainData(rainData ?? []); + const rainMap = new Map(rainDataFormatted.map((item) => [item.label, item.barVal0])); + + rsltDataFormatted.forEach((item) => { + const matchedVal = rainMap.get(item.label); + if (matchedVal !== undefined) { + item.barVal0 = matchedVal; + } + }); + + setChartData(rsltDataFormatted);// 차트 데이터 설정 + + const avgRdii = Number(qtyData?.rdiih ?? 0); + const appliedRainfall = Number(qtyData?.raintotal ?? 0); + const rdiiPerRainfall = + qtyData?.raintotal && qtyData?.raintotal !== 0 + ? Math.round(avgRdii * Number(qtyData?.sustaintime ?? 0) / qtyData.raintotal) + : 0; + + setRdiiQty(Math.round(avgRdii * 24).toLocaleString('ko-KR')); + setRainTotal(appliedRainfall.toLocaleString('ko-KR')); + setTotalVolume(rdiiPerRainfall.toLocaleString('ko-KR')); + } catch (error) { + console.error('rdiiRsltChart 에러:', error); + setChartData([]); + setRdiiQty('0'); + setRainTotal('0'); + setTotalVolume('0'); + } + }; + + const renderInfoInput = (label: string, value?: string | number | null) => ( + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]"> + {label} + </Label> + <Input + className="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white px-3 text-xl text-[#2b3440]" + placeholder="" + value={value ?? ''} + readOnly + /> + </div> + ); - rsltDataformatted.forEach(item => { - const matchedVal = rainMap.get(item.label); - if (matchedVal !== undefined) { - item.barVal0 = matchedVal; - } - }); - setChartData(rsltDataformatted); // 차트 데이터 설정 - } const renderBody = () => ( - <> - <div className="text-black text-3xl font-bold mb-5">RDII분석결과</div> - - <div className='grid grid-cols-[1fr_3fr] gap-4'> - <div className="grid grid-rows-4 gap-4 mb-4"> - <Button className="bg-white text-white w-[130px] font-bold px-4 mt-2 py-2" onClick={openPopup}> - 분석 입력현황 조회 - </Button> - {/* 상수사용량 적용연월 */} - <div className="bg-white px-4 py-2 "> - <span className="text-2xl text-red-600 font-bold">● 상수사용량 적용연월</span> - <div className="flex items-center mt-2"> - <span className="ml-5 text-black text-xl font-bold"> - {(aplcnYm)? moment(aplcnYm, "YYYYMM").format("YYYY-MM"):""} - </span> - </div> - </div> - - {/* 강우유입수량 분석결과 */} - <div className="bg-white px-4 py-2 "> - <span className="text-2xl text-red-600 font-bold">● 강우유입수량 분석결과</span> - <div className="flex items-center mt-2"> - <span className="w-[95px] inline-block text-right text-black text-xl">평균RDII : </span> - <span className="w-[45px] inline-block text-right text-black text-xl font-bold"> {rdiiQty}</span> - <span className="ml-2 text-black"> m³/d</span> - </div> - <div className="flex items-center mt-2"> - <span className="w-[95px] inline-block text-right text-black text-xl">적용강우량 : </span> - <span className="w-[45px] inline-block text-right text-black text-xl font-bold"> {rainTotal}</span> - <span className="ml-2 text-black"> mm</span> - </div> - <div className="flex items-center mt-2"> - <span className="w-[95px] inline-block text-right text-black text-xl">강우량대비RDII : </span> - <span className="w-[45px] inline-block text-right text-black text-xl font-bold"> {totalVolume}</span> - <span className="ml-2 text-black"> m³/mm</span> - </div> - </div> - </div> - {/* 차트 */} - <div className="h-[90%] bg-white text-2xl"> - <MainChart chartConfig={chartConfig} chartForm={chartForm} chartData={chartData} chartTitle={'RDII 그래프'} - yFieldText2="강수량(mm)" yFieldText="유량(m³/h)" /> - </div> + <div className="text-black"> + <div className="mb-4 flex items-start justify-between"> + <div className="text-[18px] font-semibold text-[#111827]" /> </div> - {popupOpen && ( - <div - className="absolute bg-gray-100 border border-gray-300 w-[440px] shadow-lg p-6 rounded text-2xl" - style={{ - top: popupPosition.top, - left: popupPosition.left, - zIndex: 1000, - }} - > - <div className="font-bold mb-6 text-3xl">강우유입수분석 입력현황</div> - <div className='flex flex-col'> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">상수사용월:</Label> - <SelectBox - className="w-[220px] mr-8 z-[51] mb-2" - selectClass="rounded-lg bg-white" - label='' - selectArray={aplcnYms} - value={aplcnYm} - onSelectValue={(val) => { - setAplcnYm(String(val)); - }} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">분석정보:</Label> - <SelectBox - className="w-[220px] mr-8 z-[51] mb-2" - selectClass="rounded-lg bg-white" - label='' - selectArray={rdiiAnlsInfos} - value={rainInfwtAnlsSn} - onSelectValue={(val) => { - setRainInfwtAnlsSn(Number(val)); - }} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">오수전환율:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={rdiiAnls?.drtwSwtcRt ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">수용가-맨홀 매칭:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={rdiiAnls?.atflMpngNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">관망도 파일명:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={rdiiAnls?.atflInpNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">강우 파일명:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={rdiiAnls?.atflRainNm ?? ""} - readOnly={true} - /> - </div> - <div className='flex items-center justify-between'> - <Label className="mr-2 w-auto text-2xl text-gray-600 whitespace-nowrap">RTK계수 출처:</Label> - <Input - className="mr-8 w-[220px] !text-xl placeholder:text-center mb-2" - placeholder="" - value={rdiiAnls?.srcNm ?? ""} - readOnly={true} - /> + + <div className="grid min-h-[68vh] grid-cols-[minmax(0,36%)_minmax(0,64%)] gap-5"> + <section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5"> + <h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black"> + 강우유입수분석 선택 조건 + </h3> + + <div className="rounded-[6px] border border-[#d9dde3] bg-[#eff1f4] px-4 py-6"> + <div className="space-y-3"> + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]"> + 상수사용월: + </Label> + <SelectBox + className="w-full" + selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl" + label="" + selectArray={aplcnYms} + value={aplcnYm} + onSelectValue={handleAplcnYmChange} + /> + </div> + + <div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3"> + <Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]"> + 분석정보: + </Label> + <SelectBox + className="w-full" + selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl" + label="" + selectArray={rdiiAnlsInfos} + value={rainInfwtAnlsSn} + onSelectValue={handleRdiiAnlsSnChange} + /> + </div> + + {renderInfoInput('오수전환율:', rdiiAnls?.drtwSwtcRt ?? '')} + {renderInfoInput('수용가-맨홀 매칭:', rdiiAnls?.atflMpngNm ?? '')} + {renderInfoInput('관망도 파일명:', rdiiAnls?.atflInpNm ?? '')} + {renderInfoInput('강우 파일명:', rdiiAnls?.atflRainNm ?? '')} + {renderInfoInput('RTK계수 출처:', rdiiAnls?.srcNm ?? '')} </div> </div> - <div className="flex justify-end mt-4 space-x-2"> - <button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}> - 닫기 - </button> - <button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={rdiiRsltChart}> - 선택조건 적용 - </button> + + <div className="absolute inset-x-0 bottom-0 mb-[40px] flex justify-center"> + <Button size="lg" onClick={fetchFilteredData}> + 조회 + </Button> +    + <Button size="lg" color="gray" onClick={resetState}> + 초기화 + </Button> </div> - </div>)} - {/* </Modal > */} - </> - ) + </section> + + <section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5"> + <h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black"> + 강우유입수분석 결과 + </h3> + + <div className="mb-6 flex gap-5"> + {/* 좌측 카드 */} + <div className="flex min-h-[98px] min-w-0 basis-2/5 grow shrink-[1.4] items-center justify-between rounded-[16px] border border-[#5fa3ea] bg-white px-6 py-4"> + <div className="text-[16px] font-bold text-[#ef4444]"> + 상수사용량 적용연월 + </div> + <div className="text-[24px] font-extrabold leading-none text-[#111]"> + {aplcnYm ? moment(aplcnYm, 'YYYYMM').format('YYYY-MM') : '-'} + </div> + </div> + + {/* 우측 카드 */} + <div className="flex min-h-[98px] min-w-0 basis-3/5 grow shrink items-center gap-5 rounded-[16px] border border-[#5fa3ea] bg-white px-6 py-4"> + {/* 좌측: 제목 (세로 가운데 정렬) */} + <div className="flex min-w-0 basis-[32%] items-center"> + <div className="text-[16px] font-bold text-[#ef4444] break-keep"> + 강우유입수량 분석결과 + </div> + </div> + + {/* 우측: 지표 영역 */} + <div className="flex min-w-0 basis-[68%] flex-col gap-[2px]"> + <div className="flex min-w-0 items-center gap-3"> + <div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]"> + 평균RDII + </div> + <div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums"> + {rdiiQty} + </div> + <div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]"> + m³/d + </div> + </div> + + <div className="flex min-w-0 items-center gap-3"> + <div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]"> + 적용 강우량 + </div> + <div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums"> + {rainTotal} + </div> + <div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]"> + mm + </div> + </div> + + <div className="flex min-w-0 items-center gap-3"> + <div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]"> + 강우량 대비 RDII + </div> + <div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums"> + {totalVolume} + </div> + <div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]"> + m³/mm + </div> + </div> + </div> + </div> + </div> + + <div className="rounded-[10px] bg-white px-2 pb-2 pt-3"> + {hasChartData ? ( + <MainChart + chartConfig={chartConfig} + chartForm={chartForm} + chartData={chartData} + chartTitle={'RDII 그래프'} + yFieldText2="강수량(mm)" + yFieldText="유량(m³/h)" + /> + ) : ( + <div className="flex h-[464px] items-center justify-center text-[18px] text-[#2c2f33]"> + 데이터가 없습니다. + </div> + )} + </div> + </section> + </div> + </div> + ); return ( <UiModal @@ -284,17 +341,17 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - min-h-[50vh] - w-[55vw] - max-w-none - max-h-none - overflow-visible - z-[50] - `} + w-[78vw] + max-w-none + min-w-[1100px] + min-h-[72vh] + overflow-visible + z-[50] + `} body={renderBody()} - noFooterYn = 'Y' + noFooterYn="Y" /> - ) -} + ); +}; -export default React.memo(RdiiChart); \ No newline at end of file +export default React.memo(RdiiChart); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx index e0fe03a92..11e61b006 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx @@ -28,6 +28,10 @@ import { VerticalTable } from "@/components/ui/verticalTable"; import { useGisContext } from "@common/business_components/ui/map/Gis"; import { Button } from "@common/components/ui"; import { useEffect, useRef, useState, useMemo } from "react"; +import Style from "ol/style/Style.js"; +import Fill from "ol/style/Fill.js"; +import Stroke from "ol/style/Stroke.js"; +import CircleStyle from "ol/style/Circle.js"; import { getLayerContentComponent } from "../common"; import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table"; import InfilChart from './chart/infilChart'; @@ -38,6 +42,27 @@ import { codeMapper } from "../common/codeMapper"; import LoadingOverlay from '@/components/ui/LoadingBar'; import { searchUpperPpls } from "./smh"; +const linkedFacilityHighlightStyle = new Style({ + image: new CircleStyle({ + radius: 18, + fill: new Fill({ + color: 'rgba(255, 235, 59, 0.35)', + }), + stroke: new Stroke({ + color: 'rgba(255, 193, 7, 0.45)', + width: 2, + }), + }), + fill: new Fill({ + color: 'rgba(255, 235, 59, 0.2)', + }), + stroke: new Stroke({ + color: 'rgba(255, 193, 7, 0.35)', + width: 2.5, + }), + zIndex: 9999, +}); + const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; }; @@ -59,6 +84,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const [isLoading, setIsLoading] = useState(false); // 로딩 상태 const [chmbrXcnts, setChmbrXcnts] = useState<string>(''); // 로딩 상태 const [chmbrYdnts, setChmbrYdnts] = useState<string>(''); // 로딩 상태 + const linkedFacilityFeatureRef = useRef<any>(null); + const linkedFacilityOriginalStyleRef = useRef<any>(null); // 🎯 관로관리번호 추출 const pplMngNo = featureData?.values_?.fclt_mng_no || ""; @@ -77,6 +104,70 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, }; }, [featureData?.values_]); + const clearLinkedFacilityHighlight = () => { + if (linkedFacilityFeatureRef.current) { + linkedFacilityFeatureRef.current.setStyle(linkedFacilityOriginalStyleRef.current ?? undefined); + linkedFacilityFeatureRef.current.changed?.(); + } + + linkedFacilityFeatureRef.current = null; + linkedFacilityOriginalStyleRef.current = null; + }; + + // 하수처리구역에 연결된 하수처리시설 피처를 찾아 기존 아이콘 위에 노랑 하이라이트를 덧씌운다. + const highlightLinkedFacility = (facilityCode: string | null | undefined) => { + clearLinkedFacilityHighlight(); + + if (!gisMap || !facilityCode) return; + + const normalizedFacilityCode = String(facilityCode).trim(); + const layers = gisMap.getLayers().getArray(); + + for (const layer of layers) { + const source = layer?.getSource?.(); + if (!source?.getFeatures) continue; + + const features = source.getFeatures(); + const targetFeature = features.find((feature: any) => { + const featureId = feature?.getId?.(); + const isFacilityLayer = typeof featureId === 'string' && featureId.startsWith('tb_il_swage_prcs_fclt.'); + const featureFacilityCode = feature?.get?.('swtrmt_fclt_cd'); + + return isFacilityLayer && String(featureFacilityCode ?? '').trim() === normalizedFacilityCode; + }); + + if (targetFeature) { + const originalFeatureStyle = targetFeature.getStyle?.(); + const featureLayer = targetFeature.getLayer?.(); + const layerStyleFn = featureLayer?.getStyleFunction?.() ?? null; + + linkedFacilityOriginalStyleRef.current = originalFeatureStyle; + linkedFacilityFeatureRef.current = targetFeature; + targetFeature.setStyle((feature: any, resolution: number) => { + let styles: any[] = []; + + if (typeof originalFeatureStyle === 'function') { + const originalStyles = originalFeatureStyle(feature, resolution); + if (Array.isArray(originalStyles)) styles.push(...originalStyles); + else if (originalStyles) styles.push(originalStyles); + } else if (originalFeatureStyle) { + if (Array.isArray(originalFeatureStyle)) styles.push(...originalFeatureStyle); + else styles.push(originalFeatureStyle); + } else if (layerStyleFn) { + const layerStyles = layerStyleFn(feature, resolution); + if (Array.isArray(layerStyles)) styles.push(...layerStyles); + else if (layerStyles) styles.push(layerStyles); + } + + styles.push(linkedFacilityHighlightStyle); + return styles; + }); + targetFeature.changed?.(); + return; + } + } + }; + // 피쳐데이터의 현재 레이어 이름 가져오는 함수 const getSelectedLayers = () => { let result = null; @@ -235,6 +326,17 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, ); if (materialCodeItem) data.mqlt_cd = `${materialCodeItem.text}`; } + } else { + // 중점우수토실인 경우 구분 값 + if (selectedLayerName === '중점우수토실' && data) { + if (data.mesure_spot_se) { + const mesureSpotSe = [{id:'0', text: '합류식'}, {id:'1', text: '분류식'}] + const mesureSpotSeItem = mesureSpotSe.find( + code => code.id === data.mesure_spot_se.trim() + ); + if (mesureSpotSeItem) data.mesure_spot_se = mesureSpotSeItem.text; + } + } } // 🎯 기본 데이터 포맷팅 @@ -337,6 +439,26 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, fetchData(); }, [featureId, processedData]); + useEffect(() => { + const layerTypeNm = featureData?.values_ + ? featureData?.id_?.split('.')?.[0] + : featureData?.lyrTypeNm; + + if (layerTypeNm === 'tb_ms_swage_prcs_zone') { + const facilityCode = featureData?.values_?.swtrmt_fclt_cd ?? featureData?.swtrmtFcltCd; + highlightLinkedFacility(facilityCode); + return () => { + clearLinkedFacilityHighlight(); + }; + } + + clearLinkedFacilityHighlight(); + + return () => { + clearLinkedFacilityHighlight(); + }; + }, [featureData, gisMap]); + // 저장 시 모달 띄우기 const handleSaveClick = () => { setIsModalOpen(true); // 모달 열기 @@ -514,4 +636,4 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, </div> ) } -export default FeatureClickEventPopup; \ No newline at end of file +export default FeatureClickEventPopup; diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx index f814092e7..2a0b11682 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx @@ -41,7 +41,8 @@ export async function searchUpperPpls(gisMap: any, fcltMngNo: any) { } // JSON 파싱 시도 - data = JSON.parse(text); + const response = JSON.parse(text); + data = response.data; } catch (error) { console.error("JSON parsing error:", error); alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`); diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 90ec5a120..75b2d573e 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -171,6 +171,10 @@ const Map = () => { setIsPlaying={setIsPlaying as any} targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any} checkInterval={2500} + showZoomLevel={true} + + defaultZoom={7.5} + defaultLonLat={[127.75, 36]} /> </div> </> diff --git a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx index 6d1507f06..c21dc8ca4 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/basicTab.tsx @@ -34,7 +34,7 @@ const BasicTab: React.FC = () => { const { basicType, setBasicType } = useSearchModalStore(); return ( - <div className="relative border rounded-lg p-5 w-full h-[10vh]"> + <div className="relative border rounded-lg pt-5 px-5 w-full min-h-[10vh]"> {/* 구분 */} <div className="mb-3 flex items-center justify-between gap-4"> <div className="flex items-center"> diff --git a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx index 692551cbb..63c18875d 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/operationTab.tsx @@ -37,7 +37,7 @@ const OperationTab: React.FC = () => { })); return ( - <div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]"> + <div className="relative flex flex-col border rounded-lg pt-5 px-5 w-full min-h-[10vh]"> <div className="flex items-center mb-3"> <Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">업무구분</Label> <SelectBox diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportFilesModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportFilesModal.tsx index 8be73ba1e..2b7218332 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportFilesModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportFilesModal.tsx @@ -92,10 +92,10 @@ const ReportFilesModal: React.FC<ModalOpenProp> = ({ clsfSystmSn }: ModalOpenPro onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - min-h-[50vh] h-[570px] - min-w-[35vw] w-[400px] - max-w-none max-h-none - overflow-visible + min-h-[50vh] max-h-[90dvh] + max-w-[750px] + h-auto w-auto + overflow-auto z-[5000] flex items-start `} diff --git a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx index bb6b4cd89..ad57c0a5f 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/reportTab.tsx @@ -72,15 +72,15 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg } = useSearchModalStore(); const { ctpv, sgg } = useStore(); const { - isOpen: isReportFilesModalOpen, + isOpen: isReportFilesModalOpen, setIsOpen: setIsReportFilesModalOpen, dcTree: reportOriginData, fetchDcTree - } = useReportTabStore(); // 모달 상태 관리 스토어 - const { REPORT_TYPES, flattenFolders, addFileName } = useReportTab(); - - useEffect(() => { - fetchDcTree("기본계획"); // 하수정비기본계획 report + } = useReportTabStore(); // 모달 상태 관리 스토어 + const { REPORT_TYPES, flattenFolders, addFileName } = useReportTab(); + + useEffect(() => { + fetchDcTree("기본계획"); // 하수정비기본계획 report }, []); useEffect(() => { @@ -206,7 +206,7 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg return ( <> - <div className={`relative flex flex-col border rounded-lg p-5 w-full ${isSearch ? "h-[10vh]" : "h-[6vh]"}`}> + <div className={`relative flex flex-col border rounded-lg pt-5 px-5 w-full ${isSearch ? "min-h-[10vh]" : "min-h-[6vh]"}`}> {isSearch && ( <> <div className="flex items-center mb-3"> @@ -249,7 +249,7 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg <LoadingOverlay isLoading={isLoading} /> <UiTable headName={(() => { - return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>); + return (<>조회 결과</>); })() } tableData={tableData} diff --git a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx index 8da8911e5..3ce334518 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx +++ b/apps/sn/app/(map-layout)/map/searchModal/searchModal.tsx @@ -125,13 +125,12 @@ const SearchModal: React.FC = () => { {/* 📋 결과 리스트 */} {/* 기본정보 탭 */} {activeTab === 'basic' && ( - <div className="h-[79vh] w-full overflow-hidden rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <UiTable + <div className="h-[79dvh] w-full rounded-lg border mt-4 overflow-x-scroll"> + <LoadingOverlay isLoading={isLoading} /> + <UiTable className='w-[120%]' headName={(() => { const label = BASIC_TYPES.find((type) => type.val === basicType)?.text - return (<>{label} 조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>); + return (<>{label} 조회 결과</>); })() } tableData={tableData} @@ -150,66 +149,65 @@ const SearchModal: React.FC = () => { </div> } /> - </div> </div> )} {/* 유지관리 정보 탭 */} {activeTab === 'operation' && ( <> - <div className="h-[39vh] w-full overflow-hidden rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <UiTable - headName={ - (() => { - const label = WORK_TYPES.find((type) => type.val === workType)?.text - return (<>{label} 사업 현황 ({tableCount.toLocaleString('ko-KR')})건</>); - })() - } - tableData={tableData} - headlist={dynamicHeadlist} - pageSize={OPERATION_TABLE_SIZE} - total={tableCount} - cellClick={handleOperationCellClick} - onPageChange={setCurPage} - resetPageNo={isResetPage} - resetSelection={isResetSelection} - curPage={curPage} - headButton={ - <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-sm font-medium green" size="sm" onClick={operationExcelDownload}>파일 다운로드</Button> - } - /> - </div> + <div className="h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border mt-4"> + {/* <div className="flex-1 px-2 pb-2"> */} + <LoadingOverlay isLoading={isLoading} /> + <UiTable + headName={ + (() => { + const label = WORK_TYPES.find((type) => type.val === workType)?.text + return (<>{label} 사업 현황</>); + })() + } + tableData={tableData} + headlist={dynamicHeadlist} + pageSize={OPERATION_TABLE_SIZE} + total={tableCount} + cellClick={handleOperationCellClick} + onPageChange={setCurPage} + resetPageNo={isResetPage} + resetSelection={isResetSelection} + curPage={curPage} + headButton={ + <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-sm font-medium green" size="sm" onClick={operationExcelDownload}>파일 다운로드</Button> + } + /> + {/* </div> */} </div> - <div className="h-[39vh] w-full overflow-hidden rounded-lg border"> + <div className="h-[39dvh] w-full overflow-x-hidden overflow-y-auto rounded-lg border"> {/* 테이블2 영역 */} - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isDetailLoading} /> - <UiTable - headName={ - (() => { - const label = WORK_TYPES.find((type) => type.val === workType)?.text - return (<>{label} 결과표 ({detailTableCount.toLocaleString('ko-KR')})건</>); - })() - } - tableData={detailTableData} - headlist={dynamicHeadlist2} - pageSize={OPERATION_TABLE_SIZE} - total={detailTableCount} - cellClick={handleOperationResultCellClick} - onPageChange={setDetailCurPage} - resetPageNo={isResetDetailPage} - resetSelection={isResetDetailSelection} - curPage={detailCurPage} - headButton={ - <div className='flex'> - <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button> - <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium green" size="sm" onClick={operationResultExcelDownload}>파일 다운로드</Button> - </div> - } - /> - </div> + {/* <div className="flex-1 px-2 pb-2"> */} + <LoadingOverlay isLoading={isDetailLoading} /> + <UiTable + headName={ + (() => { + const label = WORK_TYPES.find((type) => type.val === workType)?.text + return (<>{label} 결과표</>); + })() + } + tableData={detailTableData} + headlist={dynamicHeadlist2} + pageSize={OPERATION_TABLE_SIZE} + total={detailTableCount} + cellClick={handleOperationResultCellClick} + onPageChange={setDetailCurPage} + resetPageNo={isResetDetailPage} + resetSelection={isResetDetailSelection} + curPage={detailCurPage} + headButton={ + <div className='flex'> + <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button> + <Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium green" size="sm" onClick={operationResultExcelDownload}>파일 다운로드</Button> + </div> + } + /> + {/* </div> */} </div> </> )} @@ -217,23 +215,23 @@ const SearchModal: React.FC = () => { ); return ( - <> + <div className='overflow-x-auto overflow-y-hidden'> <UiModal open={isSearchModalOpen} onOpenChange={setIsSearchModalOpen} bodyClass={` h-[100dvh] - w-[60dvw] - max-w-[100dvw] - max-h-[100dvh] - overflow-hidden + w-[90dvw] + max-w-[1220px] + max-h-[970px] + overflow-y-hidden z-[101] `} body={renderBody()} useDirectRender={true} /> <Toaster position="top-center"></Toaster> - </> + </div> ); }; diff --git a/apps/sn/app/(map-layout)/map/searchModal/useBasicTab.ts b/apps/sn/app/(map-layout)/map/searchModal/useBasicTab.ts index 80d3deb50..9184286ed 100644 --- a/apps/sn/app/(map-layout)/map/searchModal/useBasicTab.ts +++ b/apps/sn/app/(map-layout)/map/searchModal/useBasicTab.ts @@ -14,7 +14,7 @@ */ import { downloadExcelWithKoreanHeaders } from '@common/business_components/ui'; import { - MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, + MS_SWAGE_PPL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_SHAPE, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, MsSwagePplExcelCol, SwtrmtFcltExcelCol, SwtrmtPrcsZoneExcelCol, MsSwageMhlExcelCol, ExclnSewagePumpExcelCol, stofcbrExcelCol } from '../components/common/table'; import { getData } from "@/components/lib/api"; @@ -46,6 +46,7 @@ export default function useBasicTab() { } = useSearchModalStore(); const { ctpv, sgg } = useStore(); + const fetchBasicFilteredData = async () => { let cqlFilters: string[] = []; const queryParams = new URLSearchParams({ @@ -79,8 +80,15 @@ export default function useBasicTab() { if (!sggCd && code) sggCd = code.slice(0, 5); } - const matchedMhlCode = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim()); - const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd); + const matchedMqltCode = basicType === 'tb_ms_swage_ppl' + ? MS_SWAGE_PPL_CODES.find(code => code.val === item?.mqltCd?.trim()) // 하수관로 재질 + : basicType === 'tb_ms_swage_mhl' + ? MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim()) // 맨홀 재질 + : undefined; + const matchedMhlUsgCode = MS_SWAGE_MHL_USE_CODES.find(code => code.val === item?.usgCd); // 맨홀 용도 + const matchedPplShpCode = MS_SWAGE_PPL_SHAPE.find(code => code.val === item?.fcltyShpCd); // 하수관로 시설물형태코드 + + const ctpvNm = ctpv.find(code => code.val.trim() === ctpvCd)?.text || item?.ctpvNm; const sggNm = sgg.find(code => code.val.trim() === sggCd)?.text || item?.sggNm; const usgCdObj = MS_SWAGE_PPL_USE_CODES.find(code => code.val === item?.usgCd?.trim()); @@ -90,15 +98,17 @@ export default function useBasicTab() { ...formattedItem, rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가 scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item?.scsYn, // scsYn이 null이면 'N'으로 변경 - mqltCd: matchedMhlCode ? matchedMhlCode.text : item?.mqltCd, + mqltCd: matchedMqltCode ? matchedMqltCode.text : item?.mqltCd, usgCd: matchedMhlUsgCode ? matchedMhlUsgCode.text : (usgCdObj ? usgCdObj.text : item?.usgCd), ctpvNm: ctpvNm ?? '-', sggNm: sggNm ?? '-', swtrmt_zone_nm: item.swtrmtZoneNm || item.swtrmt_zone_nm || '-', + fcltyShpCd: matchedPplShpCode? matchedPplShpCode.text : item?.fcltyShpCd, }; }); setTableData(dataWithRowNumber); + // console.log(tableData); setTableCount(res.count || []); }; @@ -150,7 +160,7 @@ export default function useBasicTab() { // 하수관로 cqlFilters = swagePplCqlFilters() headerMapping = MsSwagePplExcelCol; - headerRange = "A1:M1"; + headerRange = "A1:Q1"; break; } case 'tb_il_swage_prcs_fclt': { @@ -196,6 +206,8 @@ export default function useBasicTab() { const mhlUsgCdMap = new Map(MS_SWAGE_MHL_USE_CODES.map(code => [code.val?.trim(), code.text])); const pplUsgCdMap = new Map(MS_SWAGE_PPL_USE_CODES.map(code => [code.val?.trim(), code.text])); + const pplMqltCdMap = new Map(MS_SWAGE_PPL_CODES.map(code => [code.val?.trim(), code.text])); + const pplShpCdMap = new Map(MS_SWAGE_PPL_SHAPE.map(code => [code.val?.trim(), code.text])); // 관로 시설물형태코드 const ctpvMap = new Map(ctpv.map(code => [code.val?.trim(), code.text])); const sggMap = new Map(sgg.map(code => [code.val?.trim(), code.text])); @@ -232,6 +244,10 @@ export default function useBasicTab() { tmptMhlMngNo: formattedItem.tmptMhlMngNo ?? item.tmptMhlMngNo ?? '-', scendDpth: formattedItem.scendDpth ?? item.scendDpth ?? '-', scendMhlMngNo: formattedItem.scendMhlMngNo ?? item.scendMhlMngNo ?? '-', + vrtcLen: formattedItem.vrtcLen ?? item.vrtcLen ?? '-', + wdthLen: formattedItem.wdthLen ?? item.wdthLen ?? '-', + fcltyShpCd: pplShpCdMap.get(item?.fcltyShpCd?.trim()) || item.fcltyShpCd || formattedItem.fcltyShpCd || '-', + mqltCd: pplMqltCdMap.get(item?.mqltCd?.trim()) || item.mqltCd || formattedItem.mqltCd || '-', }; } case 'tb_il_swage_prcs_fclt': { @@ -329,4 +345,4 @@ export default function useBasicTab() { return { fetchBasicFilteredData, handleBasicCellClick, basicExcelDownload }; -}; \ No newline at end of file +}; diff --git a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx index 6a5c3f893..4ea769ff7 100644 --- a/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx +++ b/apps/sn/app/(map-layout)/map/waterDepth/waterDepth.tsx @@ -16,8 +16,8 @@ import { Label } from "@common/components/ui/label"; import { useEffect } from "react"; export const WATER_DEPTH_LAYER_TYPES: string[] = [ - 'tb_ms_flood_dngr_map', - 'tb_ms_sbmrg_dngr_map' + 'tb_ms_flood_dngr_map', // 홍수위험지도 + 'tb_ms_sbmrg_dngr_map' // 침수위험지도 ]; interface WaterDepthProps { diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index c791a3f12..835584e75 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -100,7 +100,7 @@ const Result = () => { const [sggCd, setSggCd] = useState<string>('0'); // 시군구 코드 상태 const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태 - const {ctpv, sgg} = useStore(); + const { ctpv, sgg } = useStore(); useEffect(() => { // setCurPage(1); @@ -166,7 +166,7 @@ const Result = () => { sggNm: sggOne ? sggOne.text : item.sggCd, }; }) - .filter((item: any) => item.scsYn === 'Y'); + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -304,9 +304,9 @@ const Result = () => { onDeleteFile={handleFileDelete} selectedRowData={selectedRowData} > - {[<div key='table1' className="h-[58vh] flex flex-col border rounded-lg bg-white shadow-md"> + {[<div key='table1' className="flex-1 flex flex-col border rounded-lg bg-white shadow-md min-h-0"> {/* <div className="mb-[1.2rem] text-xl font-medium">○ RDII 분석결과 현황</div> */} - <div className="flex-grow overflow-auto"> + <div className="flex-1 min-h-0"> <UiTable headName='' headlist={tableColumn} diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index 832809dcc..eb9f679b5 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -96,7 +96,7 @@ const Result = () => { const [ctpvCd, setCtpvCd] = useState<string>('0'); // 시도 코드 상태 const [sggCd, setSggCd] = useState<string>('0'); // 시군구 코드 상태 const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태 - const {ctpv, sgg} = useStore(); + const { ctpv, sgg } = useStore(); useEffect(() => { // setCurPage(1); @@ -159,7 +159,7 @@ const Result = () => { sggNm: sggOne ? sggOne.text : item.sggCd, }; }) - .filter((item: any) => item.scsYn === 'Y'); + .filter((item: any) => item.scsYn === 'Y'); // 선택된 행 초기화 setSelectedRowData(null); @@ -296,9 +296,9 @@ const Result = () => { onDeleteFile={handleFileDelete} selectedRowData={selectedRowData} > - {[<div key='table1' className="h-[58vh] flex flex-col border rounded-lg bg-white shadow-md"> + {[<div key='table1' className="flex-1 flex flex-col border rounded-lg bg-white shadow-md min-h-0"> {/* <div className="mb-[1.2rem] text-xl font-medium">○ 침입수 산정결과 현황</div> */} - <div className="flex-grow overflow-auto"> + <div className="flex-1 min-h-0"> <UiTable headName='' headlist={tableColumn} diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx index ada56d443..4b1db105b 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx @@ -281,6 +281,7 @@ const Page = () => { max-h-[100dvh] overflow-hidden z-[101] + flex flex-col gap-[10px] `} body={<TableModal fileNm={fileNm} atflId={atflId} />} useDirectRender={true} diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx index ad8fcf72f..97e9d788d 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/tableModal.tsx @@ -219,10 +219,10 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => { /> {/* 관리SHEET 탭 */} {activeTab === 'management' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> + <div className="h-[79vh] rounded-lg border flex flex-col"> + <div className="flex-1 min-w-[50vw] max-w-[100vw] overflow-auto"> <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[550%]"> + <div className="min-w-max"> <UiTable headName={'관리SHEET'} tableData={managementTableData} @@ -231,6 +231,7 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => { total={0} cellClick={undefined} onPageChange={() => { }} + className="whitespace-nowrap" /> </div> </div> diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx index 6f26f70ec..a078a9795 100644 --- a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx @@ -276,6 +276,7 @@ const Page = () => { max-h-[100dvh] overflow-hidden z-[101] + flex flex-col gap-[10px] `} body={<TableModal fileNm={fileNm} atflId={atflId} />} useDirectRender={true} diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx index dece4c4f8..707f2d526 100644 --- a/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx @@ -3,7 +3,7 @@ * 노후 하수관로 엑셀 데이터 테이블 모달 컴포넌트 * * - 엑셀 파일 다운로드 및 특정 시트 파싱 기능 - * - 탭 기반 테이블 표시 (row data, 정밀조사, 정비사업 등) + * - 탭 기반 테이블 표시 (raw data, 정밀조사, 정비사업 등) * - 데이터 필터링 및 빈 행 제거 처리 * - 로딩 오버레이 및 에러 핸들링 지원 * - XLSX 라이브러리를 통한 엑셀 데이터 변환 @@ -21,7 +21,7 @@ import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeCo import { oldPpTable2HeadList, oldPpTable2NodeList, oldPpTable6HeadList, oldPpTable6NodeList } from './headList'; const TABS = [ - { val: "1", text: "row data" }, + { val: "1", text: "raw data" }, { val: "2", text: "1.정밀조사" }, { val: "3", text: "2.정밀조사_정비사업연계" }, { val: "4", text: " 3.정비사업" }, @@ -74,15 +74,15 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => { type: blob.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); - const targetSheets = ['row data', '1.정밀조사', '2.정밀조사_정비사업연계', ' 3.정비사업', '4.정비사업요약', '4.정비사업최종']; + const targetSheets = ['raw data', '1.정밀조사', '2.정밀조사_정비사업연계', ' 3.정비사업', '4.정비사업요약', '4.정비사업최종']; const jsonDataBySheet = await filePickedSelectedSheets(file, 0, targetSheets); - const header1 = jsonDataBySheet['row data']?.[1] || []; + const header1 = jsonDataBySheet['raw data']?.[1] || []; const header2 = jsonDataBySheet['1.정밀조사']?.[1] || []; const header4 = jsonDataBySheet[' 3.정비사업']?.[1] || []; const header5 = jsonDataBySheet['4.정비사업요약']?.[2] || []; - // row data + // raw data const allEntries1 = Object.entries(header1); const filteredHeader1 = [ allEntries1[0], @@ -111,8 +111,8 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => { }); setTableHeader1(filteredHeader1); - const data1 = Array.isArray(jsonDataBySheet['row data']) - ? jsonDataBySheet['row data'].slice(2) + const data1 = Array.isArray(jsonDataBySheet['raw data']) + ? jsonDataBySheet['raw data'].slice(2) : []; const filteredData1 = filterEmptyRows(data1).map((item: any, index: number) => ({ ...item, @@ -177,303 +177,303 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => { rowNumber: index + 1 }; }); - setTableData3(filteredData3); + setTableData3(filteredData3); - // 3.정비사업 - const allEntries4 = Object.entries(header4); - const filteredHeader4 = allEntries4.filter((e): e is [string, any] => Boolean(e)) - .map(([key, value]) => ({ - id: key, - title: String(value).replace(/\r\n/g, '\n') - })); + // 3.정비사업 + const allEntries4 = Object.entries(header4); + const filteredHeader4 = allEntries4.filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n') + })); - filteredHeader4.splice(0, 0, { - id: "rowNumber", - title: '일련번호' - }); - setTableHeader4(filteredHeader4); + filteredHeader4.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader4(filteredHeader4); - const data4 = Array.isArray(jsonDataBySheet[' 3.정비사업']) - ? jsonDataBySheet[' 3.정비사업'].slice(2) - : []; - const filteredData4 = filterEmptyRows(data4).map((item: any, index: number) => ({ - ...item, - rowNumber: index + 1, - __EMPTY_2: item?.__EMPTY_2 || '-', - __EMPTY_11: item?.__EMPTY_11 || '-', - })); - setTableData4(filteredData4); - - // 4.정비사업요약 - const allEntries5 = Object.entries(header5); - const excludeKeys5 = [ - ": \"정비사업\"sheet에서 사업코드 ctrl+c, Ctrl+v 한 다음 중복된 데이터 제거실시", - "__EMPTY", - "__EMPTY_8", - "__EMPTY_15" - ]; - const filteredHeader5 = allEntries5 - .filter(([key]) => !excludeKeys5.includes(key)) - .filter((e): e is [string, any] => Boolean(e)) - .map(([key, value]) => ({ - id: key, - title: String(value).replace(/\r\n/g, '\n'), - })); - filteredHeader5.splice(0, 0, { - id: "rowNumber", - title: '일련번호' - }); - setTableHeader5(filteredHeader5); - - const data5 = Array.isArray(jsonDataBySheet['4.정비사업요약']) - ? jsonDataBySheet['4.정비사업요약'].slice(3) - : []; - const filteredData5 = - filterEmptyRows(data5).map((item: any, index: number) => ({ + const data4 = Array.isArray(jsonDataBySheet[' 3.정비사업']) + ? jsonDataBySheet[' 3.정비사업'].slice(2) + : []; + const filteredData4 = filterEmptyRows(data4).map((item: any, index: number) => ({ ...item, rowNumber: index + 1, + __EMPTY_2: item?.__EMPTY_2 || '-', + __EMPTY_11: item?.__EMPTY_11 || '-', })); - setTableData5(filteredData5); + setTableData4(filteredData4); - // 4.정비사업최종 (헤더는 import) - const data6 = Array.isArray(jsonDataBySheet['4.정비사업최종']) - ? jsonDataBySheet['4.정비사업최종'].slice(6) - : []; - const filteredData6 = - filterEmptyRows(data6).map((item: any, index: number) => ({ - ...item, - rowNumber: index + 1, - })); - setTableData6(filteredData6); - } catch (error) { - console.error('파일 처리 중 오류 발생:', error); - alert('파일 처리 중 오류가 발생했습니다.'); - } finally { - setIsLoading(false); + // 4.정비사업요약 + const allEntries5 = Object.entries(header5); + const excludeKeys5 = [ + ": \"정비사업\"sheet에서 사업코드 ctrl+c, Ctrl+v 한 다음 중복된 데이터 제거실시", + "__EMPTY", + "__EMPTY_8", + "__EMPTY_15" + ]; + const filteredHeader5 = allEntries5 + .filter(([key]) => !excludeKeys5.includes(key)) + .filter((e): e is [string, any] => Boolean(e)) + .map(([key, value]) => ({ + id: key, + title: String(value).replace(/\r\n/g, '\n'), + })); + filteredHeader5.splice(0, 0, { + id: "rowNumber", + title: '일련번호' + }); + setTableHeader5(filteredHeader5); + + const data5 = Array.isArray(jsonDataBySheet['4.정비사업요약']) + ? jsonDataBySheet['4.정비사업요약'].slice(3) + : []; + const filteredData5 = + filterEmptyRows(data5).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + })); + setTableData5(filteredData5); + + // 4.정비사업최종 (헤더는 import) + const data6 = Array.isArray(jsonDataBySheet['4.정비사업최종']) + ? jsonDataBySheet['4.정비사업최종'].slice(6) + : []; + const filteredData6 = + filterEmptyRows(data6).map((item: any, index: number) => ({ + ...item, + rowNumber: index + 1, + })); + setTableData6(filteredData6); + } catch (error) { + console.error('파일 처리 중 오류 발생:', error); + alert('파일 처리 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } } -} -function filterEmptyRows(data: any[]) { - return data - .filter(row => { - return Object.entries(row).some(([key, value]) => { - const match = key.match(/^__EMPTY_(\d+)$/); - if (!match || parseInt(match[1] ?? '', 10) < 2) return false; + function filterEmptyRows(data: any[]) { + return data + .filter(row => { + return Object.entries(row).some(([key, value]) => { + const match = key.match(/^__EMPTY_(\d+)$/); + if (!match || parseInt(match[1] ?? '', 10) < 2) return false; - return value !== undefined && - value !== null && - String(value).trim() !== '' && - String(value).trim() !== '-' && - String(value).trim() !== ''; - }); - }) - .map((row) => { - const processedRow = { ...row }; - const keys = Object.keys(processedRow); + return value !== undefined && + value !== null && + String(value).trim() !== '' && + String(value).trim() !== '-' && + String(value).trim() !== ''; + }); + }) + .map((row) => { + const processedRow = { ...row }; + const keys = Object.keys(processedRow); - keys.forEach((key, keyIndex) => { - // 마지막 속성(사업 근거)이 아닌 경우에만 빈 값을 '-'으로 변경 - if (keyIndex !== keys.length - 1) { - const value = processedRow[key]; - if (value === undefined || - value === null || - value === '' || - String(value).trim() === '') { - processedRow[key] = '-'; + keys.forEach((key, keyIndex) => { + // 마지막 속성(사업 근거)이 아닌 경우에만 빈 값을 '-'으로 변경 + if (keyIndex !== keys.length - 1) { + const value = processedRow[key]; + if (value === undefined || + value === null || + value === '' || + String(value).trim() === '') { + processedRow[key] = '-'; + } } - } - }); + }); - return processedRow; + return processedRow; + }); + }; + + // 특정 시트만 선택적으로 JSON으로 변환하는 함수 + function filePickedSelectedSheets(file: File, headerNum: number = 0, sheetNames: string[]): Promise<Record<string, any[]>> { + const reader = new FileReader(); + + return new Promise((resolve, reject) => { + reader.onload = function (e) { + const data = e.target?.result; + if (!data) { + console.error("Failed to read file: e.target.result is null"); + reject(new Error("Failed to read file")); + return; + } + + // Excel 파일 읽기 + const wb = XLSX.read(data, { type: 'binary', cellDates: true }); + const result: Record<string, any[]> = {}; + + // 지정된 시트 이름만 찾아서 처리 + sheetNames.forEach(targetSheetName => { + // 대소문자 구분 없이 시트 이름 찾기 + const sheetName = wb.SheetNames.find(name => + name.toLowerCase() === targetSheetName.toLowerCase() + ); + + if (sheetName) { + const sheet = wb.Sheets[sheetName]; + if (sheet) { + const jsonData = XLSX.utils.sheet_to_json(sheet, { range: headerNum, raw: false }); + result[targetSheetName] = jsonData; + } + } else { + console.warn(`Sheet "${targetSheetName}" not found in workbook`); + result[targetSheetName] = []; + } + }); + + resolve(result); + }; + + reader.onerror = function () { + console.error("Error reading file"); + reject(new Error("Error reading file")); + }; + + // 파일 읽기 시작 + reader.readAsBinaryString(file); }); -}; - -// 특정 시트만 선택적으로 JSON으로 변환하는 함수 -function filePickedSelectedSheets(file: File, headerNum: number = 0, sheetNames: string[]): Promise<Record<string, any[]>> { - const reader = new FileReader(); - - return new Promise((resolve, reject) => { - reader.onload = function (e) { - const data = e.target?.result; - if (!data) { - console.error("Failed to read file: e.target.result is null"); - reject(new Error("Failed to read file")); - return; - } - - // Excel 파일 읽기 - const wb = XLSX.read(data, { type: 'binary', cellDates: true }); - const result: Record<string, any[]> = {}; - - // 지정된 시트 이름만 찾아서 처리 - sheetNames.forEach(targetSheetName => { - // 대소문자 구분 없이 시트 이름 찾기 - const sheetName = wb.SheetNames.find(name => - name.toLowerCase() === targetSheetName.toLowerCase() - ); - - if (sheetName) { - const sheet = wb.Sheets[sheetName]; - if (sheet) { - const jsonData = XLSX.utils.sheet_to_json(sheet, { range: headerNum, raw: false }); - result[targetSheetName] = jsonData; - } - } else { - console.warn(`Sheet "${targetSheetName}" not found in workbook`); - result[targetSheetName] = []; - } - }); - - resolve(result); - }; - - reader.onerror = function () { - console.error("Error reading file"); - reject(new Error("Error reading file")); - }; - - // 파일 읽기 시작 - reader.readAsBinaryString(file); - }); -}; + }; -return ( - <> - {/* 탭 영역 */} - <UITab - defaultValue={activeTab} - tabList={TABS.map((tab) => ({ - value: tab.val, - label: tab.text - }))} - onValueChange={(value: string) => setActiveTab(value)} - /> - {/* row data 탭 */} - {activeTab === '1' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[150%]"> - <UiTable - headName={'row data'} - tableData={tableData1} - headlist={tableHeader1} - pageSize={10} - total={0} - cellClick={undefined} - onPageChange={() => { }} - /> + return ( + <> + {/* 탭 영역 */} + <UITab + defaultValue={activeTab} + tabList={TABS.map((tab) => ({ + value: tab.val, + label: tab.text + }))} + onValueChange={(value: string) => setActiveTab(value)} + /> + {/* row data 탭 */} + {activeTab === '1' && ( + <div className="h-[79vh] rounded-lg border flex flex-col"> + <div className="flex-1 min-w-[50vw] max-w-[100vw] overflow-auto"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-max"> + <UiTable + headName={'raw data'} + tableData={tableData1} + headlist={tableHeader1} + pageSize={10} + total={0} + cellClick={undefined} + onPageChange={() => { }} + /> + </div> </div> </div> - </div> - )} - {/* 1.정밀조사 탭 */} - {activeTab === '2' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[150%]"> - <UiTable - headName={'1.정밀조사'} - tableData={tableData2} - headlist={tableHeader2} - pageSize={10} - total={0} - className={"overflow-x-auto overflow-y-auto"} - cellClick={undefined} - onPageChange={() => { }} - /> + )} + {/* 1.정밀조사 탭 */} + {activeTab === '2' && ( + <div className="h-[79vh] w-full overflow-auto rounded-lg border"> + <div className="flex-1 px-2 pb-2"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-[150%]"> + <UiTable + headName={'1.정밀조사'} + tableData={tableData2} + headlist={tableHeader2} + pageSize={10} + total={0} + className={"overflow-x-auto overflow-y-auto"} + cellClick={undefined} + onPageChange={() => { }} + /> + </div> </div> </div> - </div> - )} - {/* 2.정밀조사_정비사업연계 탭 */} - {activeTab === '3' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[550%]"> - <UiTable - headName={'2.정밀조사_정비사업연계'} - tableData={tableData3} - headlist={oldPpTable2HeadList} - pageSize={10} - total={0} - className={"overflow-x-auto overflow-y-auto"} - cellClick={undefined} - onPageChange={() => { }} - > - <HeadMakeColSpan upHeadList={oldPpTable2NodeList} /> - </UiTable> + )} + {/* 2.정밀조사_정비사업연계 탭 */} + {activeTab === '3' && ( + <div className="h-[79vh] w-full overflow-auto rounded-lg border"> + <div className="flex-1 px-2 pb-2"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-[550%]"> + <UiTable + headName={'2.정밀조사_정비사업연계'} + tableData={tableData3} + headlist={oldPpTable2HeadList} + pageSize={10} + total={0} + className={"overflow-x-auto overflow-y-auto"} + cellClick={undefined} + onPageChange={() => { }} + > + <HeadMakeColSpan upHeadList={oldPpTable2NodeList} /> + </UiTable> + </div> </div> </div> - </div> - )} - {/* 3.정비사업 탭 */} - {activeTab === '4' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[160%]"> - <UiTable - headName={'3.정비사업'} - tableData={tableData4} - headlist={tableHeader4} - pageSize={10} - total={0} - className={"overflow-x-auto overflow-y-auto"} - cellClick={undefined} - onPageChange={() => { }} - /> + )} + {/* 3.정비사업 탭 */} + {activeTab === '4' && ( + <div className="h-[79vh] w-full overflow-auto rounded-lg border"> + <div className="flex-1 px-2 pb-2"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-[160%]"> + <UiTable + headName={'3.정비사업'} + tableData={tableData4} + headlist={tableHeader4} + pageSize={10} + total={0} + className={"overflow-x-auto overflow-y-auto"} + cellClick={undefined} + onPageChange={() => { }} + /> + </div> </div> </div> - </div> - )} - {/* 4.정비사업요약 탭 */} - {activeTab === '5' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[130%]"> - <UiTable - headName={'4.정비사업요약'} - tableData={tableData5} - headlist={tableHeader5} - pageSize={10} - total={0} - className={"overflow-x-auto overflow-y-auto"} - cellClick={undefined} - onPageChange={() => { }} - /> + )} + {/* 4.정비사업요약 탭 */} + {activeTab === '5' && ( + <div className="h-[79vh] w-full overflow-auto rounded-lg border"> + <div className="flex-1 px-2 pb-2"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-[130%]"> + <UiTable + headName={'4.정비사업요약'} + tableData={tableData5} + headlist={tableHeader5} + pageSize={10} + total={0} + className={"overflow-x-auto overflow-y-auto"} + cellClick={undefined} + onPageChange={() => { }} + /> + </div> </div> </div> - </div> - )} - {/* 4.정비사업최종 탭 */} - {activeTab === '6' && ( - <div className="h-[79vh] w-full overflow-auto rounded-lg border"> - <div className="flex-1 px-2 pb-2"> - <LoadingOverlay isLoading={isLoading} /> - <div className="min-w-[200%]"> - <UiTable - headName={'4.정비사업최종'} - tableData={tableData6} - headlist={oldPpTable6HeadList} - pageSize={10} - total={0} - className={"overflow-x-auto overflow-y-auto"} - cellClick={undefined} - onPageChange={() => { }} - > - <HeadMakeColSpan upHeadList={oldPpTable6NodeList} /> - </UiTable> + )} + {/* 4.정비사업최종 탭 */} + {activeTab === '6' && ( + <div className="h-[79vh] w-full overflow-auto rounded-lg border"> + <div className="flex-1 px-2 pb-2"> + <LoadingOverlay isLoading={isLoading} /> + <div className="min-w-[200%]"> + <UiTable + headName={'4.정비사업최종'} + tableData={tableData6} + headlist={oldPpTable6HeadList} + pageSize={10} + total={0} + className={"overflow-x-auto overflow-y-auto"} + cellClick={undefined} + onPageChange={() => { }} + > + <HeadMakeColSpan upHeadList={oldPpTable6NodeList} /> + </UiTable> + </div> </div> </div> - </div> - )} - </> -) + )} + </> + ) }; export default TableModal; \ No newline at end of file diff --git a/apps/sn/app/auth/login/route.ts b/apps/sn/app/auth/login/route.ts index d954cbaa1..65bbce15e 100644 --- a/apps/sn/app/auth/login/route.ts +++ b/apps/sn/app/auth/login/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const hostname = request.nextUrl.hostname; - const redirectUrl = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "https://queen.hasuon.or.kr"; + const redirectUrl = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "https://portal.hasuon.or.kr"; if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") { return NextResponse.redirect(new URL("/", request.url)); // 로컬 및 개발 환경 diff --git a/apps/sn/app/page.tsx b/apps/sn/app/page.tsx index 1babfe451..2a1db71d8 100644 --- a/apps/sn/app/page.tsx +++ b/apps/sn/app/page.tsx @@ -19,15 +19,14 @@ import { LoginApi } from "@/entities/auth"; import { useEffect } from "react"; import { getCookie } from "@/shared/lib"; import { AUTH_USER_ID } from "@/shared/config"; -import { useRouter } from "next/navigation"; export default function Home() { - const router = useRouter(); const checkToken = getCookie(AUTH_USER_ID); useEffect(() => { + const hostname = window.location.hostname; - + if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") { if (!checkToken) { async function doLogin() { @@ -39,7 +38,7 @@ export default function Home() { } } doLogin(); - } else router.push("/map"); - } else router.push("/map"); + } else window.location.href = "/map"; + } else window.location.href = "/map"; }, []); } diff --git a/apps/sn/middleware.ts b/apps/sn/middleware.ts index 97f4fcce7..c4da97544 100644 --- a/apps/sn/middleware.ts +++ b/apps/sn/middleware.ts @@ -44,8 +44,10 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/server")) { const decodedQuery = toDecodedQueryString(searchParams); - const newUrl = `${SERVER_API_URL}${pathname.replace("/server", "")}${decodedQuery ? `?${decodedQuery}` : "" - }`; + const replacedPath = pathname.replace("/server", ""); + // '/server//cms-service/..' 같은 케이스에서 '//' 정리 + const normalizedPath = replacedPath.replace(/^\/+/g, "/"); + const newUrl = `${SERVER_API_URL}${normalizedPath}${decodedQuery ? `?${decodedQuery}` : ""}`; console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`); const rewriteRequest = rewriteWith(newUrl, req, true); return rewriteRequest; diff --git a/apps/sn/next.config.ts b/apps/sn/next.config.ts index bbfa649d5..ac726f759 100644 --- a/apps/sn/next.config.ts +++ b/apps/sn/next.config.ts @@ -1,16 +1,48 @@ import type { NextConfig } from "next"; +const subDomain = + process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000"; + const nextConfig: NextConfig = { output: "standalone", experimental: { - proxyTimeout: 600000, // 10분 (밀리초 단위) - }, - typescript: { - ignoreBuildErrors: true, // 이 줄 추가 + proxyTimeout: 600000, }, eslint: { ignoreDuringBuilds: true, }, + typescript: { + ignoreBuildErrors: true, + }, + logging: { + fetches: { + fullUrl: true, + }, + }, + async rewrites() { + return { + beforeFiles: [ + { + source: "/_next/static/:path*", + destination: `${subDomain}/_next/static/:path*`, + has: [ + { + type: "header", + key: "referer", + value: "(.*)/cms(.*)", + }, + ], + }, + ], + afterFiles: [ + { + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, + }, + ], + fallback: [], + }; + }, }; export default nextConfig; diff --git a/apps/sn/package.json b/apps/sn/package.json index a9c3bb923..471cbe0cb 100644 --- a/apps/sn/package.json +++ b/apps/sn/package.json @@ -26,7 +26,7 @@ "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "moment": "^2.30.1", - "next": "15.1.7", + "next": "15.3.8", "react-chartjs-2": "^5.3.0", "react-hot-toast": "^2.5.2", "react-dnd": "^16.0.1", diff --git a/apps/sn/public/images/help.png b/apps/sn/public/images/help.png new file mode 100644 index 000000000..e872802e4 Binary files /dev/null and b/apps/sn/public/images/help.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png index ffc225159..81ad25a20 100644 Binary files a/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png new file mode 100644 index 000000000..991826693 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png differ diff --git a/apps/sn/src/entities/auth/lib/auth-wrapper.tsx b/apps/sn/src/entities/auth/lib/auth-wrapper.tsx index 289a97087..bc4a82aab 100644 --- a/apps/sn/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/sn/src/entities/auth/lib/auth-wrapper.tsx @@ -1,23 +1,26 @@ "use client"; import { useEffect } from "react"; -import { useRouter, usePathname } from "next/navigation"; +import { usePathname } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { useUserStore } from "@/shared/lib"; import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; +import { isAtLoginRedirectUrl } from "@common/network"; import { getCookie } from "@/shared/lib"; -import { LoginApi } from "../api/login-service"; - -const LOGIN_PAGE_URL = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "http://dev.onasys.co.kr:3000/auth/login"; // 로그인 페이지 URL +const LOGIN_PAGE_URL = + process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "/auth/login"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); const pathname = usePathname(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(LOGIN_PAGE_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -35,24 +38,27 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); + setUserInfo(null); - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); + if (isAtLoginRedirectUrl(LOGIN_PAGE_URL)) return; - const hostname = window.location.hostname; + // const hostname = window.location.hostname; - // 개발 환경에서는 localhost 도메인 사용 - if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "con.mind-one.co.kr") - router.push("/"); // 개발시 홈으로 돌아가기 + // // 개발 환경에서는 localhost 도메인 사용 + // if ( + // hostname === "localhost" || + // hostname === "127.0.0.1" || + // hostname === "con.mind-one.co.kr" + // ) + // window.location.href = "/"; - // 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용 - // router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용 - if (hostname.includes("hasuon.or.kr")) window.location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트 - } + // // 운영 환경에서는 서브도메인을 포함한 최상위 도메인 사용 + // // router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용 + // if (hostname.includes("hasuon.or.kr")) + // window.location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트 + + // 개발/운영 환경에서 로그인 URL 분기는 환경 변수로 처리 (.env.*) + window.location.assign(LOGIN_PAGE_URL); } })(); }, [pathname]); diff --git a/apps/sn/src/shared/config/api.ts b/apps/sn/src/shared/config/api.ts index 3eba9fa91..16d1b5452 100644 --- a/apps/sn/src/shared/config/api.ts +++ b/apps/sn/src/shared/config/api.ts @@ -1,5 +1,8 @@ import { PROXY_HOST } from "./env"; +// 브라우저에서는 동일 오리진으로 상대경로 호출 → CORS/localhost 고정 문제 방지 +// (Next middleware가 /server/* 요청을 백엔드로 rewrite) +// 서버 환경(SSR 등)에서는 절대 URL이 필요할 수 있어 기존 PROXY_HOST 기반 유지 export const BASE_URL = `${PROXY_HOST}/server`; export const ENDPOINT = { diff --git a/apps/cm/.env.development b/apps/st/.env.example similarity index 100% rename from apps/cm/.env.development rename to apps/st/.env.example diff --git a/apps/st/.gitignore b/apps/st/.gitignore new file mode 100644 index 000000000..3f74d9ee3 --- /dev/null +++ b/apps/st/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.env* +!.env.example +*.md + +# next-agents-md +.next-docs/ diff --git a/apps/st/.prettierrc b/apps/st/.prettierrc new file mode 100644 index 000000000..a7963dac2 --- /dev/null +++ b/apps/st/.prettierrc @@ -0,0 +1,4 @@ +{ + "plugins": ["prettier-plugin-tailwindcss"], + "tabWidth": 4 +} diff --git a/apps/st/app/(auth)/auth/layout.tsx b/apps/st/app/(auth)/auth/layout.tsx new file mode 100644 index 000000000..33651c590 --- /dev/null +++ b/apps/st/app/(auth)/auth/layout.tsx @@ -0,0 +1,17 @@ +import "@/app/styles/base.css"; + +export const metadata = { + title: "유역하수도 통합 정보플랫폼", +}; + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <html lang="ko"> + <body>{children}</body> + </html> + ); +} diff --git a/apps/st/app/(auth)/auth/login/page.tsx b/apps/st/app/(auth)/auth/login/page.tsx new file mode 100644 index 000000000..8f9e6adc6 --- /dev/null +++ b/apps/st/app/(auth)/auth/login/page.tsx @@ -0,0 +1,2 @@ +import { LoginPage } from "@/pages/auth/login"; +export default LoginPage; diff --git a/apps/st/app/(main)/(with-base-layout)/layout.tsx b/apps/st/app/(main)/(with-base-layout)/layout.tsx new file mode 100644 index 000000000..6faa92cc1 --- /dev/null +++ b/apps/st/app/(main)/(with-base-layout)/layout.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { useCallback, useEffect, useMemo } from "react"; +import { usePathname } from "next/navigation"; + +import { + getTabStoreState as getGridTabStoreState, + useTabStore as gridTabStore, +} from "@rsone/components"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +import { ENDPOINT } from "@/shared/config"; +import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib"; +import { StatisticsPageLayout } from "@/shared/ui/statistics-page-layout"; +import { MenuApi } from "@/entities/menu"; +import type { MenuItem } from "@/entities/menu"; +import { BaseLayout } from "@common/components"; +import { getConfig } from "@common/config"; + +import { getTabStoreState, useTabStore } from "@/store/tab-store"; + +const appConfig = getConfig(); +const userId = getCookie(appConfig.AUTH_USER_ID ?? ""); +const appSiteId = getCookie("site-id-key") ?? ""; +const isLoggedIn = !!userId; + +export default function WithBaseLayout({ + children, +}: { + children: React.ReactNode; +}) { + const pathname = usePathname(); + const queryClient = useQueryClient(); + + const setCurrentMenuInfo = useMenuStore( + (state) => state.setCurrentMenuInfo, + ); + const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo); + + const addTab = useTabStore((state) => state.addTab); + const setActiveTab = useTabStore((state) => state.setActiveTab); + const setMenuList = useTabStore((state) => state.setMenuList); + const activeTabId = useTabStore((state) => state.activeTabId); + const menuList = useTabStore((state) => state.menuList); + const tabs = useTabStore((state) => state.tabs); + const addTabForGrid = gridTabStore((state) => state.addTab); + const setActiveTabForGrid = gridTabStore((state) => state.setActiveTab); + const setMenuListForGrid = gridTabStore((state) => state.setMenuList); + const alert = useAlertStore((state) => state.setMessage); + + const { data: menuListData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], + queryFn: () => MenuApi.getMenuRoles(appSiteId), + enabled: isLoggedIn, + }); + + useEffect(() => { + if (menuListData) { + setMenuList(menuListData); + setMenuListForGrid(menuListData); + } + }, [menuListData, setMenuList, setMenuListForGrid]); + + // 새로고침 후 persist로 앱 탭만 복원된 경우 rsone 그리드 스토어에 탭을 맞춤 + useEffect(() => { + if (!menuList) return; + const appTabs = getTabStoreState().tabs; + if (appTabs.length === 0) return; + if (getGridTabStoreState().tabs.length > 0) return; + + for (const tab of appTabs) { + getGridTabStoreState().addTab(tab.menuItem); + } + + const activeId = getTabStoreState().activeTabId; + if (activeId) { + const activeTab = appTabs.find((t) => t.id === activeId); + const gridActiveId = activeTab?.menuItem.menuEngNm; + if (gridActiveId) { + getGridTabStoreState().setActiveTab(gridActiveId); + } + } + }, [menuList, tabs]); + + useEffect(() => { + if (!pathname) return; + + // pathname이 변경될 때만 실행되도록 tabs를 직접 가져옴 + const currentTabs = getTabStoreState().tabs; + const tab = currentTabs.find((t) => t.path === pathname); + if (tab) { + setActiveTab(tab.id); + const gridActiveId = tab.menuItem.menuEngNm; + if (gridActiveId) { + setActiveTabForGrid(gridActiveId); + } + } + }, [pathname, setActiveTab, setActiveTabForGrid]); + + // 활성 탭의 메뉴를 찾아서 urlPath를 가져옴. 탭이 전부 닫히면 ''를 넘겨 LNB에서 메뉴 강조 제거 + const currentPath = useMemo(() => { + if (tabs.length === 0) return ""; + + if (activeTabId && menuList) { + // menuList에서 활성 탭의 메뉴 찾기 + const findMenuByEngNm = (items: MenuItem[]): MenuItem | null => { + for (const item of items) { + if (item.urlPath === activeTabId) { + return item; + } + if (item.children) { + const found = findMenuByEngNm(item.children); + if (found) return found; + } + } + return null; + }; + + const activeMenu = findMenuByEngNm(menuList); + + if (activeMenu?.urlPath) { + return activeMenu.urlPath; + } + } + + // 탭에서 직접 찾기 + if (activeTabId) { + const currentTabs = getTabStoreState().tabs; + const activeTab = currentTabs.find((t) => t.id === activeTabId); + if (activeTab?.menuItem?.urlPath) { + return activeTab.menuItem.urlPath; + } + } + + // currentMenuInfo에서 가져오기 + if (currentMenuInfo?.urlPath) { + return currentMenuInfo.urlPath; + } + + // 기본값으로 pathname 사용 + return pathname ?? ""; + }, [tabs.length, activeTabId, menuList, currentMenuInfo, pathname]); + + const handleMenuClick = useCallback( + (item: MenuItem) => { + setCurrentMenuInfo(item); + const addTabResult: boolean = addTab(item); + + // packages/rsone의 스토어에도 동기화 + if (addTabResult) { + addTabForGrid(item); + } + + if (!addTabResult) { + alert(`최대 추가 할 수 있는 화면 수를 초과 하였습니다. + 기존 화면을 닫은 후 이용해주세요.`); + } + }, + [setCurrentMenuInfo, addTab, addTabForGrid, alert], + ); + + return ( + <BaseLayout + queryClient={queryClient} + currentPath={currentPath} + handleMenuClick={handleMenuClick} + > + <StatisticsPageLayout>{children}</StatisticsPageLayout> + </BaseLayout> + ); +} diff --git a/apps/st/app/(main)/(with-base-layout)/loading.tsx b/apps/st/app/(main)/(with-base-layout)/loading.tsx new file mode 100644 index 000000000..3a3b15932 --- /dev/null +++ b/apps/st/app/(main)/(with-base-layout)/loading.tsx @@ -0,0 +1,2 @@ +import { SkeletonPage } from "@/shared/ui/skeleton-page"; +export default SkeletonPage; diff --git a/apps/st/app/(main)/(with-base-layout)/statistics/page.tsx b/apps/st/app/(main)/(with-base-layout)/statistics/page.tsx new file mode 100644 index 000000000..23feaf7e1 --- /dev/null +++ b/apps/st/app/(main)/(with-base-layout)/statistics/page.tsx @@ -0,0 +1,2 @@ +import { StatisticsPage } from "@/pages/statistics"; +export default StatisticsPage; diff --git a/apps/st/app/(main)/layout.tsx b/apps/st/app/(main)/layout.tsx new file mode 100644 index 000000000..aa2049dcc --- /dev/null +++ b/apps/st/app/(main)/layout.tsx @@ -0,0 +1,8 @@ +import type { Metadata } from "next"; +import { App } from "@/app"; + +export const metadata: Metadata = { + title: "유역하수도 통합 정보플랫폼", +}; + +export default App; diff --git a/apps/st/app/api/auth/logout/route.ts b/apps/st/app/api/auth/logout/route.ts new file mode 100644 index 000000000..fdcc9df7c --- /dev/null +++ b/apps/st/app/api/auth/logout/route.ts @@ -0,0 +1,17 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + REFRESH_TOKEN, +} from "@/shared/config"; + +export async function POST() { + const cookieStore = await cookies(); + cookieStore.delete(ACCESS_TOKEN); + cookieStore.delete(REFRESH_TOKEN); + cookieStore.delete(AUTH_USER_ID); + cookieStore.delete(CLAIM_NAME); + return NextResponse.json({ message: "Logged out" }, { status: 200 }); +} diff --git a/apps/st/app/api/csp-violation/route.ts b/apps/st/app/api/csp-violation/route.ts new file mode 100644 index 000000000..9c4fdd702 --- /dev/null +++ b/apps/st/app/api/csp-violation/route.ts @@ -0,0 +1,31 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { logCSPViolation } from '@/shared/lib/csp-utils'; + +export async function POST(request: NextRequest) { + try { + const violation = await request.json(); + + // CSP 위반 로깅 + logCSPViolation(violation); + + // 추가적인 보안 분석이나 알림 로직을 여기에 추가할 수 있습니다 + // 예: Slack 알림, 이메일 알림, 데이터베이스 저장 등 + + return NextResponse.json({ + success: true, + message: 'CSP violation logged successfully' + }); + } catch (error) { + console.error('Error processing CSP violation:', error); + return NextResponse.json( + { success: false, message: 'Failed to process CSP violation' }, + { status: 500 } + ); + } +} + +export async function GET() { + return NextResponse.json({ + message: 'CSP violation reporting endpoint is active' + }); +} \ No newline at end of file diff --git a/apps/st/app/api/proxy/[...url]/route.ts b/apps/st/app/api/proxy/[...url]/route.ts new file mode 100644 index 000000000..c9f0d3730 --- /dev/null +++ b/apps/st/app/api/proxy/[...url]/route.ts @@ -0,0 +1,106 @@ +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + REFRESH_TOKEN, + SERVER_API_URL, + SITE_ID, +} from "@/shared/config"; +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +async function handler( + request: Request, + params: { url: string[] }, + method: "GET" | "POST" | "PUT" | "DELETE", +) { + const cookieStore = await cookies(); + const headers: Record<string, string> = { + "Content-Type": "application/json", + "X-Site-Id": SITE_ID, + }; + let body = {}; + const isRefreshHandler = params.url.includes("refresh"); + + // silent refresh + if (isRefreshHandler) { + const refreshToken = cookieStore.get(REFRESH_TOKEN); + headers[CLAIM_NAME] = refreshToken?.value ?? ""; + + if (!headers[CLAIM_NAME]) { + return NextResponse.json( + { message: "Can't refresh" }, + { status: 401 }, + ); + } + } + + try { + if (!isRefreshHandler) { + body = await request.json(); + } + + const result = await fetch( + `${SERVER_API_URL}/${params.url.join("/")}`, + { + method, + headers, + body: JSON.stringify(body), + }, + ); + + if (!result.ok) { + return NextResponse.json( + { message: "Invalid Credentials" }, + { status: 401 }, + ); + } + + const accessToken = result.headers.get(ACCESS_TOKEN); + const refreshToken = result.headers.get(REFRESH_TOKEN); + const userId = result.headers.get(AUTH_USER_ID); + + if (accessToken && userId) { + const response = NextResponse.json({ + [ACCESS_TOKEN]: accessToken, + [AUTH_USER_ID]: userId, + }); + + if (refreshToken) { + cookieStore.set(REFRESH_TOKEN, refreshToken, { + httpOnly: true, + sameSite: "lax", + path: "/", + }); + } + + return response; + } + + return NextResponse.json( + { message: "Invalid Credentials" }, + { status: 401 }, + ); + } catch (error) { + return NextResponse.json( + { message: "Server Error", error }, + { status: 500 }, + ); + } +} + +export async function POST( + request: Request, + props: { params: Promise<{ url: string[] }> }, +) { + const params = await props.params; + return handler(request, params, "POST"); +} + +export async function PUT( + request: Request, + props: { params: Promise<{ url: string[] }> }, +) { + const params = await props.params; + return handler(request, params, "PUT"); +} diff --git a/apps/st/docs/csp-setup.md b/apps/st/docs/csp-setup.md new file mode 100644 index 000000000..0bd7e5714 --- /dev/null +++ b/apps/st/docs/csp-setup.md @@ -0,0 +1,124 @@ +# Content Security Policy (CSP) - img-src 설정 가이드 + +## 개요 +이 문서는 CS 앱에서 Content Security Policy의 img-src 지시어를 설정하고 관리하는 방법을 설명합니다. + +## 설정된 CSP 정책 + +### 기본 img-src 정책 +```javascript +img-src 'self' data: https: blob:; +``` + +### 환경별 설정 +- **개발 환경**: `img-src 'self' data: https: blob: http://localhost:*;` +- **프로덕션 환경**: `img-src 'self' data: https: blob:;` + +## 구현 방법 + +### 1. next.config.ts에서 설정 (권장) +```typescript +async headers() { + return [ + { + source: "/(.*)", + headers: [ + { + key: "Content-Security-Policy", + value: "img-src 'self' data: https: blob:;" + } + ] + } + ]; +} +``` + +### 2. 미들웨어에서 설정 +```typescript +function addSecurityHeaders(response: NextResponse): NextResponse { + response.headers.set("Content-Security-Policy", "img-src 'self' data: https: blob:;"); + return response; +} +``` + +## img-src 지시어 설명 + +### 허용된 소스 +- `'self'`: 같은 출처(도메인)의 이미지 허용 +- `data:`: Data URL 이미지 허용 (base64 인코딩된 이미지) +- `https:`: HTTPS 프로토콜의 모든 도메인에서 이미지 허용 +- `blob:`: Blob URL 이미지 허용 (파일 업로드 등) + +### 추가 도메인 허용 +```javascript +// 특정 도메인 허용 +img-src 'self' data: https: blob: https://images.example.com; + +// 여러 도메인 허용 +img-src 'self' data: https: blob: https://cdn.example.com https://images.example.com; +``` + +## 문제 해결 + +### 일반적인 img-src 위반 + +1. **외부 이미지 로드 실패** + - 해결: 해당 도메인을 img-src에 추가 + - 예: `https://example.com`을 img-src에 추가 + +2. **Data URL 이미지 차단** + - 해결: `data:`가 img-src에 포함되어 있는지 확인 + +3. **Blob URL 이미지 차단** + - 해결: `blob:`이 img-src에 포함되어 있는지 확인 + +### 디버깅 방법 + +1. **브라우저 개발자 도구 확인** + - Console 탭에서 CSP 위반 메시지 확인 + - Network 탭에서 차단된 이미지 확인 + +2. **CSP 위반 로그 확인** + - 서버 로그에서 CSP 위반 보고 확인 + - `/api/csp-violation` 엔드포인트 로그 확인 + +## 유틸리티 함수 사용 + +### 도메인 추가 +```typescript +import { addImageDomain } from '@/shared/config/csp'; + +// 개발 환경에 도메인 추가 +const newPolicy = addImageDomain('https://example.com', 'development'); +``` + +### 정책 검증 +```typescript +import { validateImgSrcPolicy } from '@/shared/lib/csp-utils'; + +const isValid = validateImgSrcPolicy('img-src self data: https:;'); +``` + +## 보안 권장사항 + +1. **최소 권한 원칙** + - 필요한 도메인만 허용 + - 불필요한 도메인 제거 + +2. **정기적인 정책 검토** + - 사용하지 않는 도메인 제거 + - 새로운 이미지 소스 추가 시 검토 + +3. **HTTPS 사용 권장** + - 가능한 한 HTTPS 도메인만 허용 + - HTTP는 개발 환경에서만 사용 + +## 환경 변수 + +- `DISABLE_CSP=true` - 개발 환경에서 CSP 비활성화 +- `NODE_ENV` - 환경별 정책 적용 + +## 추가 리소스 + +- [MDN CSP img-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src) +- [CSP Evaluator](https://csp-evaluator.withgoogle.com/) \ No newline at end of file diff --git a/apps/st/eslint.config.mjs b/apps/st/eslint.config.mjs new file mode 100644 index 000000000..55ef02fb5 --- /dev/null +++ b/apps/st/eslint.config.mjs @@ -0,0 +1,33 @@ +import { FlatCompat } from "@eslint/eslintrc"; +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import baseEsLintConfig from "../../eslint.config.mjs"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + ...baseEsLintConfig, + { + rules: { + "react-hooks/exhaustive-deps": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-wrapper-object-types": "warn", + }, + }, +]; + +export default eslintConfig; diff --git a/apps/st/middleware.ts b/apps/st/middleware.ts new file mode 100644 index 000000000..9a2a48cb1 --- /dev/null +++ b/apps/st/middleware.ts @@ -0,0 +1,147 @@ +import { NextRequest, NextResponse } from "next/server"; +import { + CLAIM_NAME, + AUTH_USER_ID, + PORT, + PROXY_HOST, + SERVER_API_URL, + SITE_ID, + ST_SERVICE_URL, +} from "./src/shared/config"; + +const serverApiUrl = + process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000"; + +const stServiceUrl = ST_SERVICE_URL; + +const linkBackendApiHost = process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST ?? "http://localhost:8080"; + +// CSP 헤더 설정 함수 (img-src만) +function addSecurityHeaders(response: NextResponse): NextResponse { + response.headers.set("Content-Security-Policy", "img-src 'self' data: https: blob:;"); + return response; +} + +export function middleware(req: NextRequest) { + const pathname = req.nextUrl.pathname; + const searchParams = req.nextUrl.searchParams; + + const logMessages: string[] = [ + `[Middleware Request] ${new Date().toISOString()}`, + `[Environment]\n Mode: ${process.env.NODE_ENV}\n Port: ${PORT}\n Proxy Host: ${PROXY_HOST}\n Server API URL: ${SERVER_API_URL}\n Site ID: ${SITE_ID}\n ST Service URL: ${stServiceUrl}`, + `[Request]\n Original Pathname: ${pathname}`, + ]; + + // ST Service 처리 (dc의 /server/dc-service 패턴과 동일) + if (pathname.startsWith("/server/st-management-service") || pathname.startsWith("/server/st-input-service")) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + + const queryParams = searchParams.toString(); + const newUrl = `${stServiceUrl}${pathname.replace("/server", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + logMessages.push(` Token Debug: ${token ? "EXISTS" : "MISSING"}`); + logMessages.push(` UserId Debug: ${userId ? "EXISTS" : "MISSING"}`); + logMessages.push(` ST Service Rewritten URL: ${newUrl}`); + logMessages.push("\n--------------------------------\n"); + console.log(logMessages.join("\n")); + + const response = NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + return addSecurityHeaders(response); + } + + // 일반 Server API 처리 (ST 서비스 제외) + if ( + pathname.startsWith("/server") && + !pathname.startsWith("/server/st-management-service") && + !pathname.startsWith("/server/st-input-service") + ) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + + const queryParams = searchParams.toString(); + const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ + queryParams ? `?${queryParams}` : "" + }`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + logMessages.push(` Rewritten URL: ${newUrl}`); + logMessages.push("\n--------------------------------\n"); + console.log(logMessages.join("\n")); + + const response = NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + return addSecurityHeaders(response); + } + + if (pathname.startsWith("/attach-service")) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + const newUrl = `${serverApiUrl}${pathname}`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + logMessages.push(` Rewritten URL: ${newUrl}`); + logMessages.push("\n--------------------------------\n"); + console.log(logMessages.join("\n")); + + const response = NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + return addSecurityHeaders(response); + } + + if (pathname.startsWith("/api/link")) { + const requestHeaders = new Headers(req.headers); + const token = req.cookies.get(CLAIM_NAME)?.value; + const userId = req.cookies.get(AUTH_USER_ID)?.value; + + // /api/link 경로를 제거하고 백엔드 API 경로로 변환 + const apiPath = pathname.replace("/api/link", ""); + const queryParams = searchParams.toString(); + const newUrl = `${linkBackendApiHost}${apiPath}${ + queryParams ? `?${queryParams}` : "" + }`; + + if (token) requestHeaders.set(CLAIM_NAME, token); + if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + logMessages.push(` Link API Rewritten URL: ${newUrl}`); + logMessages.push("\n--------------------------------\n"); + console.log(logMessages.join("\n")); + + const response = NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + return addSecurityHeaders(response); + } + + if (pathname === "/") { + const response = NextResponse.redirect(new URL("/statistics", req.url)); + return addSecurityHeaders(response); + } + + logMessages.push("\n--------------------------------\n"); + console.log(logMessages.join("\n")); + + const response = NextResponse.next(); + return addSecurityHeaders(response); +} + +export const config = { + matcher: ["/server/:path*", "/attach-service/:path*", "/api/link/:path*", "/"], +}; diff --git a/apps/st/next.config.ts b/apps/st/next.config.ts new file mode 100644 index 000000000..a0adf6946 --- /dev/null +++ b/apps/st/next.config.ts @@ -0,0 +1,47 @@ +import type { NextConfig } from "next"; + +const subDomain = + process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000"; + +const nextConfig: NextConfig = { + // output: "standalone", + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { + key: "Content-Security-Policy", + value: "img-src 'self' data: https: blob:;", + }, + ], + }, + ]; + }, + async rewrites() { + return { + beforeFiles: [ + { + source: "/_next/static/:path*", + destination: `${subDomain}/_next/static/:path*`, + has: [ + { + type: "header", + key: "referer", + value: "(.*)/cms(.*)", + }, + ], + }, + ], + afterFiles: [ + { + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, + }, + ], + fallback: [], + }; + }, +}; + +export default nextConfig; diff --git a/apps/st/package.json b/apps/st/package.json new file mode 100644 index 000000000..9880cab30 --- /dev/null +++ b/apps/st/package.json @@ -0,0 +1,74 @@ +{ + "name": "st", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@common/assets": "workspace:*", + "@common/components": "workspace:*", + "@common/config": "workspace:*", + "@faker-js/faker": "^9.9.0", + "@fontsource-variable/noto-sans-kr": "^5.2.5", + "@frontend-opensource/use-react-hooks": "^1.1.4", + "@fullcalendar/core": "^6.1.17", + "@fullcalendar/daygrid": "^6.1.17", + "@fullcalendar/interaction": "^6.1.17", + "@fullcalendar/react": "^6.1.17", + "@fullcalendar/timegrid": "^6.1.17", + "@hookform/resolvers": "^4.1.3", + "@minoru/react-dnd-treeview": "3.5.1-alpha.1", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", + "@rsone/components": "workspace:*", + "@tanstack/react-query": "^5.67.1", + "@tanstack/react-query-devtools": "^5.75.5", + "@tanstack/react-table": "^8.21.2", + "@tinymce/tinymce-react": "^5.1.1", + "@types/xlsx": "^0.0.36", + "axios": "^1.8.1", + "chart.js": "^4.4.8", + "chartjs-plugin-zoom": "^2.2.0", + "dayjs": "^1.11.13", + "js-cookie": "^3.0.5", + "jszip": "^3.10.1", + "lucide-react": "^0.474.0", + "next": "15.3.8", + "next-qrcode": "^2.5.1", + "next-themes": "^0.4.6", + "react-chartjs-2": "^5.3.0", + "react-dnd": "^16.0.1", + "react-hook-form": "^7.54.2", + "realgrid": "^2.9.5", + "realgrid-react": "^0.9.7", + "remixicon": "^4.7.0", + "sonner": "^2.0.1", + "swr": "^2.3.7", + "tailwindcss-animate": "^1.0.7", + "tus-js-client": "^4.3.1", + "xlsx": "^0.18.5", + "zod": "^3.24.2", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.13.1", + "@types/webpack": "^5.28.5", + "eslint": "^9", + "eslint-config-next": "15.1.7", + "eslint-plugin-react-hooks": "^5.0.0", + "postcss": "^8", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "steiger": "^0.5.5", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/apps/st/pages/.gitkeep b/apps/st/pages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/st/postcss.config.mjs b/apps/st/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/apps/st/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/st/public/favicon.ico b/apps/st/public/favicon.ico new file mode 100644 index 000000000..d304b6981 Binary files /dev/null and b/apps/st/public/favicon.ico differ diff --git a/apps/st/public/images/bg_login.jpg b/apps/st/public/images/bg_login.jpg new file mode 100644 index 000000000..b7793d16a Binary files /dev/null and b/apps/st/public/images/bg_login.jpg differ diff --git a/apps/st/public/images/help.png b/apps/st/public/images/help.png new file mode 100644 index 000000000..e872802e4 Binary files /dev/null and b/apps/st/public/images/help.png differ diff --git a/apps/st/public/images/img_cc_by.png b/apps/st/public/images/img_cc_by.png new file mode 100644 index 000000000..628cc2bfd Binary files /dev/null and b/apps/st/public/images/img_cc_by.png differ diff --git a/apps/st/public/images/img_cc_by_nc.png b/apps/st/public/images/img_cc_by_nc.png new file mode 100644 index 000000000..86f60c5db Binary files /dev/null and b/apps/st/public/images/img_cc_by_nc.png differ diff --git a/apps/st/public/images/img_cc_by_nc_nd.png b/apps/st/public/images/img_cc_by_nc_nd.png new file mode 100644 index 000000000..9d8ccb18d Binary files /dev/null and b/apps/st/public/images/img_cc_by_nc_nd.png differ diff --git a/apps/st/public/images/img_cc_by_nc_sa.png b/apps/st/public/images/img_cc_by_nc_sa.png new file mode 100644 index 000000000..00f402c9e Binary files /dev/null and b/apps/st/public/images/img_cc_by_nc_sa.png differ diff --git a/apps/st/public/images/img_cc_by_nd.png b/apps/st/public/images/img_cc_by_nd.png new file mode 100644 index 000000000..0da64acd8 Binary files /dev/null and b/apps/st/public/images/img_cc_by_nd.png differ diff --git a/apps/st/public/images/img_cc_by_sa.png b/apps/st/public/images/img_cc_by_sa.png new file mode 100644 index 000000000..6d94e60b9 Binary files /dev/null and b/apps/st/public/images/img_cc_by_sa.png differ diff --git a/apps/st/public/images/img_opentype01.png b/apps/st/public/images/img_opentype01.png new file mode 100644 index 000000000..456c7c6bb Binary files /dev/null and b/apps/st/public/images/img_opentype01.png differ diff --git a/apps/st/public/images/img_opentype02.png b/apps/st/public/images/img_opentype02.png new file mode 100644 index 000000000..a6c5bfb32 Binary files /dev/null and b/apps/st/public/images/img_opentype02.png differ diff --git a/apps/st/public/images/img_opentype03.png b/apps/st/public/images/img_opentype03.png new file mode 100644 index 000000000..970a6a0d4 Binary files /dev/null and b/apps/st/public/images/img_opentype03.png differ diff --git a/apps/st/public/images/img_opentype04.png b/apps/st/public/images/img_opentype04.png new file mode 100644 index 000000000..41d284cab Binary files /dev/null and b/apps/st/public/images/img_opentype04.png differ diff --git a/apps/st/public/tinymce/icons/default/icons.min.js b/apps/st/public/tinymce/icons/default/icons.min.js new file mode 100644 index 000000000..2cf9ef86d --- /dev/null +++ b/apps/st/public/tinymce/icons/default/icons.min.js @@ -0,0 +1 @@ +tinymce.IconManager.add("default",{icons:{"accessibility-check":'<svg width="24" height="24"><path d="M12 2a2 2 0 012 2 2 2 0 01-2 2 2 2 0 01-2-2c0-1.1.9-2 2-2zm8 7h-5v12c0 .6-.4 1-1 1a1 1 0 01-1-1v-5c0-.6-.4-1-1-1a1 1 0 00-1 1v5c0 .6-.4 1-1 1a1 1 0 01-1-1V9H4a1 1 0 110-2h16c.6 0 1 .4 1 1s-.4 1-1 1z" fill-rule="nonzero"/></svg>',"action-next":'<svg width="24" height="24"><path fill-rule="nonzero" d="M5.7 7.3a1 1 0 00-1.4 1.4l7.7 7.7 7.7-7.7a1 1 0 10-1.4-1.4L12 13.6 5.7 7.3z"/></svg>',"action-prev":'<svg width="24" height="24"><path fill-rule="nonzero" d="M18.3 15.7a1 1 0 001.4-1.4L12 6.6l-7.7 7.7a1 1 0 001.4 1.4L12 9.4l6.3 6.3z"/></svg>',"align-center":'<svg width="24" height="24"><path d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm3 4h8c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 110-2zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 010-2zm-3-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2z" fill-rule="evenodd"/></svg>',"align-justify":'<svg width="24" height="24"><path d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm0 4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm0 4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2zm0 4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2z" fill-rule="evenodd"/></svg>',"align-left":'<svg width="24" height="24"><path d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm0 4h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2zm0-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2z" fill-rule="evenodd"/></svg>',"align-none":'<svg width="24" height="24"><path d="M14.2 5L13 7H5a1 1 0 110-2h9.2zm4 0h.8a1 1 0 010 2h-2l1.2-2zm-6.4 4l-1.2 2H5a1 1 0 010-2h6.8zm4 0H19a1 1 0 010 2h-4.4l1.2-2zm-6.4 4l-1.2 2H5a1 1 0 010-2h4.4zm4 0H19a1 1 0 010 2h-6.8l1.2-2zM7 17l-1.2 2H5a1 1 0 010-2h2zm4 0h8a1 1 0 010 2H9.8l1.2-2zm5.2-13.5l1.3.7-9.7 16.3-1.3-.7 9.7-16.3z" fill-rule="evenodd"/></svg>',"align-right":'<svg width="24" height="24"><path d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm6 4h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm-6-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2z" fill-rule="evenodd"/></svg>',"arrow-left":'<svg width="24" height="24"><path d="M5.6 13l12 6a1 1 0 001.4-1V6a1 1 0 00-1.4-.9l-12 6a1 1 0 000 1.8z" fill-rule="evenodd"/></svg>',"arrow-right":'<svg width="24" height="24"><path d="M18.5 13l-12 6A1 1 0 015 18V6a1 1 0 011.4-.9l12 6a1 1 0 010 1.8z" fill-rule="evenodd"/></svg>',bold:'<svg width="24" height="24"><path d="M7.8 19c-.3 0-.5 0-.6-.2l-.2-.5V5.7c0-.2 0-.4.2-.5l.6-.2h5c1.5 0 2.7.3 3.5 1 .7.6 1.1 1.4 1.1 2.5a3 3 0 01-.6 1.9c-.4.6-1 1-1.6 1.2.4.1.9.3 1.3.6s.8.7 1 1.2c.4.4.5 1 .5 1.6 0 1.3-.4 2.3-1.3 3-.8.7-2.1 1-3.8 1H7.8zm5-8.3c.6 0 1.2-.1 1.6-.5.4-.3.6-.7.6-1.3 0-1.1-.8-1.7-2.3-1.7H9.3v3.5h3.4zm.5 6c.7 0 1.3-.1 1.7-.4.4-.4.6-.9.6-1.5s-.2-1-.7-1.4c-.4-.3-1-.4-2-.4H9.4v3.8h4z" fill-rule="evenodd"/></svg>',bookmark:'<svg width="24" height="24"><path d="M6 4v17l6-4 6 4V4c0-.6-.4-1-1-1H7a1 1 0 00-1 1z" fill-rule="nonzero"/></svg>',"border-style":'<svg width="24" height="24"><g fill-rule="evenodd"><rect width="18" height="2" x="3" y="6" rx="1"/><rect width="2.8" height="2" x="3" y="16" rx="1"/><rect width="2.8" height="2" x="6.8" y="16" rx="1"/><rect width="2.8" height="2" x="10.6" y="16" rx="1"/><rect width="2.8" height="2" x="14.4" y="16" rx="1"/><rect width="2.8" height="2" x="18.2" y="16" rx="1"/><rect width="8" height="2" x="3" y="11" rx="1"/><rect width="8" height="2" x="13" y="11" rx="1"/></g></svg>',"border-width":'<svg width="24" height="24"><g fill-rule="evenodd"><rect width="18" height="5" x="3" y="5" rx="1"/><rect width="18" height="3.5" x="3" y="11.5" rx="1"/><rect width="18" height="2" x="3" y="17" rx="1"/></g></svg>',brightness:'<svg width="24" height="24"><path d="M12 17c.3 0 .5.1.7.3.2.2.3.4.3.7v1c0 .3-.1.5-.3.7a1 1 0 01-.7.3 1 1 0 01-.7-.3 1 1 0 01-.3-.7v-1c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3zm0-10a1 1 0 01-.7-.3A1 1 0 0111 6V5c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3.3 0 .5.1.7.3.2.2.3.4.3.7v1c0 .3-.1.5-.3.7a1 1 0 01-.7.3zm7 4c.3 0 .5.1.7.3.2.2.3.4.3.7 0 .3-.1.5-.3.7a1 1 0 01-.7.3h-1a1 1 0 01-.7-.3 1 1 0 01-.3-.7c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h1zM7 12c0 .3-.1.5-.3.7a1 1 0 01-.7.3H5a1 1 0 01-.7-.3A1 1 0 014 12c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h1c.3 0 .5.1.7.3.2.2.3.4.3.7zm10 3.5l.7.8c.2.1.3.4.3.6 0 .3-.1.6-.3.8a1 1 0 01-.8.3 1 1 0 01-.6-.3l-.8-.7a1 1 0 01-.3-.8c0-.2.1-.5.3-.7a1 1 0 011.4 0zm-10-7l-.7-.8a1 1 0 01-.3-.6c0-.3.1-.6.3-.8.2-.2.5-.3.8-.3.2 0 .5.1.7.3l.7.7c.2.2.3.5.3.8 0 .2-.1.5-.3.7a1 1 0 01-.7.3 1 1 0 01-.8-.3zm10 0a1 1 0 01-.8.3 1 1 0 01-.7-.3 1 1 0 01-.3-.7c0-.3.1-.6.3-.8l.8-.7c.1-.2.4-.3.6-.3.3 0 .6.1.8.3.2.2.3.5.3.8 0 .2-.1.5-.3.7l-.7.7zm-10 7c.2-.2.5-.3.8-.3.2 0 .5.1.7.3a1 1 0 010 1.4l-.8.8a1 1 0 01-.6.3 1 1 0 01-.8-.3 1 1 0 01-.3-.8c0-.2.1-.5.3-.6l.7-.8zM12 8a4 4 0 013.7 2.4 4 4 0 010 3.2A4 4 0 0112 16a4 4 0 01-3.7-2.4 4 4 0 010-3.2A4 4 0 0112 8zm0 6.5c.7 0 1.3-.2 1.8-.7.5-.5.7-1.1.7-1.8s-.2-1.3-.7-1.8c-.5-.5-1.1-.7-1.8-.7s-1.3.2-1.8.7c-.5.5-.7 1.1-.7 1.8s.2 1.3.7 1.8c.5.5 1.1.7 1.8.7z" fill-rule="evenodd"/></svg>',browse:'<svg width="24" height="24"><path d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2h-4v-2h4V8H5v10h4v2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm-8 9.4l-2.3 2.3a1 1 0 11-1.4-1.4l4-4a1 1 0 011.4 0l4 4a1 1 0 01-1.4 1.4L13 13.4V20a1 1 0 01-2 0v-6.6z" fill-rule="nonzero"/></svg>',cancel:'<svg width="24" height="24"><path d="M12 4.6a7.4 7.4 0 110 14.8 7.4 7.4 0 010-14.8zM12 3a9 9 0 100 18 9 9 0 000-18zm0 8L14.8 8l1 1.1-2.7 2.8 2.7 2.7-1.1 1.1-2.7-2.7-2.7 2.7-1-1.1 2.6-2.7-2.7-2.7 1-1.1 2.8 2.7z" fill-rule="nonzero"/></svg>',"cell-background-color":'<svg width="24" height="24"><path d="M15.7 2l1.6 1.6-2.7 2.6 5.9 5.8c.7.7.7 1.7 0 2.4l-6.3 6.1a1.7 1.7 0 01-2.4 0l-6.3-6.1c-.7-.7-.7-1.7 0-2.4L15.7 2zM18 12l-4.5-4L9 12h9zM4 16s2 2.4 2 3.8C6 21 5.1 22 4 22s-2-1-2-2.2C2 18.4 4 16 4 16z"/></svg>',"cell-border-color":'<svg width="24" height="24"><g fill-rule="evenodd"><path fill-rule="nonzero" d="M5 13v5h2v2H5a2 2 0 01-2-2v-5h2zm8-7V4h6a2 2 0 012 2h-8z" opacity=".2"/><path fill-rule="nonzero" d="M13 4v2H5v7H3V6c0-1.1.9-2 2-2h8zm-2.6 14.1l.1-.1.1.1.2.3.2.2.2.2c.4.6.8 1.2.8 1.7 0 .8-.7 1.5-1.5 1.5S9 21.3 9 20.5c0-.5.4-1.1.8-1.7l.2-.2.2-.2.2-.3z"/><path d="M13 11l-2 2H5v-2h6V6h2z"/><path fill-rule="nonzero" d="M18.4 8l1 1-1.8 1.9 4 4c.5.4.5 1.1 0 1.6l-4.3 4.2a1.2 1.2 0 01-1.6 0l-4.4-4.2c-.4-.5-.4-1.2 0-1.7l7-6.8zm1.6 7l-3-3-3 3h6z"/></g></svg>',"change-case":'<svg width="24" height="24"><path d="M18.4 18.2v-.6c-.5.8-1.3 1.2-2.4 1.2-2.2 0-3.3-1.6-3.3-4.8 0-3.1 1-4.7 3.3-4.7 1.1 0 1.8.3 2.4 1.1v-.6c0-.5.4-.8.8-.8s.8.3.8.8v8.4c0 .5-.4.8-.8.8a.8.8 0 01-.8-.8zm-2-7.4c-1.3 0-1.8.9-1.8 3.2 0 2.4.5 3.3 1.7 3.3 1.3 0 1.8-.9 1.8-3.2 0-2.4-.5-3.3-1.7-3.3zM10 15.7H5.5l-.8 2.6a1 1 0 01-1 .7h-.2a.7.7 0 01-.7-1l4-12a1 1 0 012 0l4 12a.7.7 0 01-.8 1h-.2a1 1 0 01-1-.7l-.8-2.6zm-.3-1.5l-2-6.5-1.9 6.5h3.9z" fill-rule="evenodd"/></svg>',"character-count":'<svg width="24" height="24"><path d="M4 11.5h16v1H4v-1zm4.8-6.8V10H7.7V5.8h-1v-1h2zM11 8.3V9h2v1h-3V7.7l2-1v-.9h-2v-1h3v2.4l-2 1zm6.3-3.4V10h-3.1V9h2.1V8h-2.1V6.8h2.1v-1h-2.1v-1h3.1zM5.8 16.4c0-.5.2-.8.5-1 .2-.2.6-.3 1.2-.3l.8.1c.2 0 .4.2.5.3l.4.4v2.8l.2.3H8.2v-.1-.2l-.6.3H7c-.4 0-.7 0-1-.2a1 1 0 01-.3-.9c0-.3 0-.6.3-.8.3-.2.7-.4 1.2-.4l.6-.2h.3v-.2l-.1-.2a.8.8 0 00-.5-.1 1 1 0 00-.4 0l-.3.4h-1zm2.3.8h-.2l-.2.1-.4.1a1 1 0 00-.4.2l-.2.2.1.3.5.1h.4l.4-.4v-.6zm2-3.4h1.2v1.7l.5-.3h.5c.5 0 .9.1 1.2.5.3.4.5.8.5 1.4 0 .6-.2 1.1-.5 1.5-.3.4-.7.6-1.3.6l-.6-.1-.4-.4v.4h-1.1v-5.4zm1.1 3.3c0 .3 0 .6.2.8a.7.7 0 001.2 0l.2-.8c0-.4 0-.6-.2-.8a.7.7 0 00-.6-.3l-.6.3-.2.8zm6.1-.5c0-.2 0-.3-.2-.4a.8.8 0 00-.5-.2c-.3 0-.5.1-.6.3l-.2.9c0 .3 0 .6.2.8.1.2.3.3.6.3.2 0 .4 0 .5-.2l.2-.4h1.1c0 .5-.3.8-.6 1.1a2 2 0 01-1.3.4c-.5 0-1-.2-1.3-.6a2 2 0 01-.5-1.4c0-.6.1-1.1.5-1.5.3-.4.8-.5 1.4-.5.5 0 1 0 1.2.3.4.3.5.7.5 1.2h-1v-.1z" fill-rule="evenodd"/></svg>',"checklist-rtl":'<svg width="24" height="24"><path d="M5 17h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 010-2zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 110-2zm14.2 11c.2-.4.6-.5.9-.3.3.2.4.6.2 1L18 20c-.2.3-.7.4-1 0l-1.3-1.3a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8zm0-6c.2-.4.6-.5.9-.3.3.2.4.6.2 1L18 14c-.2.3-.7.4-1 0l-1.3-1.3a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8zm0-6c.2-.4.6-.5.9-.3.3.2.4.6.2 1L18 8c-.2.3-.7.4-1 0l-1.3-1.3a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8z" fill-rule="evenodd"/></svg>',checklist:'<svg width="24" height="24"><path d="M11 17h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0-6h8a1 1 0 010 2h-8a1 1 0 010-2zM7.2 16c.2-.4.6-.5.9-.3.3.2.4.6.2 1L6 20c-.2.3-.7.4-1 0l-1.3-1.3a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8zm0-6c.2-.4.6-.5.9-.3.3.2.4.6.2 1L6 14c-.2.3-.7.4-1 0l-1.3-1.3a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8zm0-6c.2-.4.6-.5.9-.3.3.2.4.6.2 1L6 8c-.2.3-.7.4-1 0L3.8 6.9a.7.7 0 010-1c.3-.2.7-.2 1 0l.7.9 1.7-2.8z" fill-rule="evenodd"/></svg>',checkmark:'<svg width="24" height="24"><path d="M18.2 5.4a1 1 0 011.6 1.2l-8 12a1 1 0 01-1.5.1l-5-5a1 1 0 111.4-1.4l4.1 4.1 7.4-11z" fill-rule="nonzero"/></svg>',"chevron-down":'<svg width="10" height="10"><path d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 010-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8z" fill-rule="nonzero"/></svg>',"chevron-left":'<svg width="10" height="10"><path d="M7.8 1.3L4 5l3.8 3.7c.3.3.3.8 0 1-.4.4-.9.4-1.2 0L2.2 5.7a.8.8 0 010-1.2L6.6.2C7 0 7.4 0 7.8.2c.3.3.3.8 0 1.1z" fill-rule="nonzero"/></svg>',"chevron-right":'<svg width="10" height="10"><path d="M2.2 1.3a.8.8 0 010-1c.4-.4.9-.4 1.2 0l4.4 4.1c.3.4.3.9 0 1.2L3.4 9.8c-.3.3-.8.3-1.2 0a.8.8 0 010-1.1L6 5 2.2 1.3z" fill-rule="nonzero"/></svg>',"chevron-up":'<svg width="10" height="10"><path d="M8.7 7.8L5 4 1.3 7.8c-.3.3-.8.3-1 0a.8.8 0 010-1.2l4.1-4.4c.3-.3.9-.3 1.2 0l4.2 4.4c.3.3.3.9 0 1.2-.3.3-.8.3-1.1 0z" fill-rule="nonzero"/></svg>',close:'<svg width="24" height="24"><path d="M17.3 8.2L13.4 12l3.9 3.8a1 1 0 01-1.5 1.5L12 13.4l-3.8 3.9a1 1 0 01-1.5-1.5l3.9-3.8-3.9-3.8a1 1 0 011.5-1.5l3.8 3.9 3.8-3.9a1 1 0 011.5 1.5z" fill-rule="evenodd"/></svg>',"code-sample":'<svg width="24" height="26"><path d="M7.1 11a2.8 2.8 0 01-.8 2 2.8 2.8 0 01.8 2v1.7c0 .3.1.6.4.8.2.3.5.4.8.4.3 0 .4.2.4.4v.8c0 .2-.1.4-.4.4-.7 0-1.4-.3-2-.8-.5-.6-.8-1.3-.8-2V15c0-.3-.1-.6-.4-.8-.2-.3-.5-.4-.8-.4a.4.4 0 01-.4-.4v-.8c0-.2.2-.4.4-.4.3 0 .6-.1.8-.4.3-.2.4-.5.4-.8V9.3c0-.7.3-1.4.8-2 .6-.5 1.3-.8 2-.8.3 0 .4.2.4.4v.8c0 .2-.1.4-.4.4-.3 0-.6.1-.8.4-.3.2-.4.5-.4.8V11zm9.8 0V9.3c0-.3-.1-.6-.4-.8-.2-.3-.5-.4-.8-.4a.4.4 0 01-.4-.4V7c0-.2.1-.4.4-.4.7 0 1.4.3 2 .8.5.6.8 1.3.8 2V11c0 .3.1.6.4.8.2.3.5.4.8.4.2 0 .4.2.4.4v.8c0 .2-.2.4-.4.4-.3 0-.6.1-.8.4-.3.2-.4.5-.4.8v1.7c0 .7-.3 1.4-.8 2-.6.5-1.3.8-2 .8a.4.4 0 01-.4-.4v-.8c0-.2.1-.4.4-.4.3 0 .6-.1.8-.4.3-.2.4-.5.4-.8V15a2.8 2.8 0 01.8-2 2.8 2.8 0 01-.8-2zm-3.3-.4c0 .4-.1.8-.5 1.1-.3.3-.7.5-1.1.5-.4 0-.8-.2-1.1-.5-.4-.3-.5-.7-.5-1.1 0-.5.1-.9.5-1.2.3-.3.7-.4 1.1-.4.4 0 .8.1 1.1.4.4.3.5.7.5 1.2zM12 13c.4 0 .8.1 1.1.5.4.3.5.7.5 1.1 0 1-.1 1.6-.5 2a3 3 0 01-1.1 1c-.4.3-.8.4-1.1.4a.5.5 0 01-.5-.5V17a3 3 0 001-.2l.6-.6c-.6 0-1-.2-1.3-.5-.2-.3-.3-.7-.3-1 0-.5.1-1 .5-1.2.3-.4.7-.5 1.1-.5z" fill-rule="evenodd"/></svg>',"color-levels":'<svg width="24" height="24"><path d="M17.5 11.4A9 9 0 0118 14c0 .5 0 1-.2 1.4 0 .4-.3.9-.5 1.3a6.2 6.2 0 01-3.7 3 5.7 5.7 0 01-3.2 0A5.9 5.9 0 017.6 18a6.2 6.2 0 01-1.4-2.6 6.7 6.7 0 010-2.8c0-.4.1-.9.3-1.3a13.6 13.6 0 012.3-4A20 20 0 0112 4a26.4 26.4 0 013.2 3.4 18.2 18.2 0 012.3 4zm-2 4.5c.4-.7.5-1.4.5-2a7.3 7.3 0 00-1-3.2c.2.6.2 1.2.2 1.9a4.5 4.5 0 01-1.3 3 5.3 5.3 0 01-2.3 1.5 4.9 4.9 0 01-2 .1 4.3 4.3 0 002.4.8 4 4 0 002-.6 4 4 0 001.5-1.5z" fill-rule="evenodd"/></svg>',"color-picker":'<svg width="24" height="24"><path d="M12 3a9 9 0 000 18 1.5 1.5 0 001.1-2.5c-.2-.3-.4-.6-.4-1 0-.8.7-1.5 1.5-1.5H16a5 5 0 005-5c0-4.4-4-8-9-8zm-5.5 9a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm3-4a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm5 0a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm3 4a1.5 1.5 0 110-3 1.5 1.5 0 010 3z" fill-rule="nonzero"/></svg>',"color-swatch-remove-color":'<svg width="24" height="24"><path stroke="#000" stroke-width="2" d="M21 3L3 21" fill-rule="evenodd"/></svg>',"color-swatch":'<svg width="24" height="24"><rect x="3" y="3" width="18" height="18" rx="1" fill-rule="evenodd"/></svg>',"comment-add":'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M9 19l3-2h7c.6 0 1-.4 1-1V6c0-.6-.4-1-1-1H5a1 1 0 00-1 1v10c0 .6.4 1 1 1h4v2zm-2 4v-4H5a3 3 0 01-3-3V6a3 3 0 013-3h14a3 3 0 013 3v10a3 3 0 01-3 3h-6.4L7 23z"/><path d="M13 10h2a1 1 0 010 2h-2v2a1 1 0 01-2 0v-2H9a1 1 0 010-2h2V8a1 1 0 012 0v2z"/></g></svg>',comment:'<svg width="24" height="24"><path fill-rule="nonzero" d="M9 19l3-2h7c.6 0 1-.4 1-1V6c0-.6-.4-1-1-1H5a1 1 0 00-1 1v10c0 .6.4 1 1 1h4v2zm-2 4v-4H5a3 3 0 01-3-3V6a3 3 0 013-3h14a3 3 0 013 3v10a3 3 0 01-3 3h-6.4L7 23z"/></svg>',contrast:'<svg width="24" height="24"><path d="M12 4a7.8 7.8 0 015.7 2.3A8 8 0 1112 4zm-6 8a6 6 0 006 6V6a6 6 0 00-6 6z" fill-rule="evenodd"/></svg>',copy:'<svg width="24" height="24"><path d="M16 3H6a2 2 0 00-2 2v11h2V5h10V3zm1 4a2 2 0 012 2v10a2 2 0 01-2 2h-7a2 2 0 01-2-2V9c0-1.2.9-2 2-2h7zm0 12V9h-7v10h7z" fill-rule="nonzero"/></svg>',crop:'<svg width="24" height="24"><path d="M17 8v7h2c.6 0 1 .4 1 1s-.4 1-1 1h-2v2c0 .6-.4 1-1 1a1 1 0 01-1-1v-2H7V9H5a1 1 0 110-2h2V5c0-.6.4-1 1-1s1 .4 1 1v2h7l3-3 1 1-3 3zM9 9v5l5-5H9zm1 6h5v-5l-5 5z" fill-rule="evenodd"/></svg>',"cut-column":'<svg width="24" height="24"><path fill-rule="evenodd" d="M7.2 4.5c.9 0 1.6.4 2.2 1A3.7 3.7 0 0110.5 8v.5l1 1 4-4 1-.5a3.3 3.3 0 012 0c.4 0 .7.3 1 .5L17 8h4v13h-6V10l-1.5 1.5.5.5v4l-2.5-2.5-1 1v.5c0 .4 0 .8-.3 1.2-.2.5-.4.9-.8 1.2-.6.7-1.3 1-2.2 1-.8.2-1.5 0-2-.6l-.5-.8-.2-1c0-.4 0-.8.3-1.2A3.9 3.9 0 017 12.7c.5-.2 1-.3 1.5-.2l1-1-1-1c-.5 0-1 0-1.5-.2-.5-.1-1-.4-1.4-.9-.4-.3-.6-.7-.8-1.2L4.5 7c0-.4 0-.7.2-1 0-.3.3-.6.5-.8.5-.5 1.2-.8 2-.7zm12.3 5h-3v10h3v-10zM8 13.8h-.3l-.4.2a2.8 2.8 0 00-.7.4v.1a2.8 2.8 0 00-.6.8l-.1.4v.7l.2.5.5.2h.7a2.6 2.6 0 00.8-.3 2.4 2.4 0 00.7-.7 2.5 2.5 0 00.3-.8 1.5 1.5 0 000-.8 1 1 0 00-.2-.4 1 1 0 00-.5-.2H8zm3.5-3.7c-.4 0-.7.1-1 .4-.3.3-.4.6-.4 1s.1.7.4 1c.3.3.6.4 1 .4s.7-.1 1-.4c.3-.3.4-.6.4-1s-.1-.7-.4-1c-.3-.3-.6-.4-1-.4zM7 5.8h-.4a1 1 0 00-.5.3 1 1 0 00-.2.5v.7a2.5 2.5 0 00.3.8l.2.3h.1l.4.4.4.2.4.1h.7L9 9l.2-.4a1.6 1.6 0 000-.8 2.6 2.6 0 00-.3-.8A2.5 2.5 0 007.7 6l-.4-.1H7z"/></svg>',"cut-row":'<svg width="24" height="24"><path fill-rule="evenodd" d="M22 3v5H9l3 3 2-2h4l-4 4 1 1h.5c.4 0 .8 0 1.2.3.5.2.9.4 1.2.8.7.6 1 1.3 1 2.2.2.8 0 1.5-.6 2l-.8.5-1 .2c-.4 0-.8 0-1.2-.3a3.9 3.9 0 01-2.1-2.2c-.2-.5-.3-1-.2-1.5l-1-1-1 1c0 .5 0 1-.2 1.5-.1.5-.4 1-.9 1.4-.3.4-.7.6-1.2.8l-1.2.3c-.4 0-.7 0-1-.2-.3 0-.6-.3-.8-.5-.5-.5-.8-1.2-.7-2 0-.9.4-1.6 1-2.2A3.7 3.7 0 018.6 14H9l1-1-4-4-.5-1a3.3 3.3 0 010-2c0-.4.3-.7.5-1l2 2V3h14zM8.5 15.3h-.3a2.6 2.6 0 00-.8.4 2.5 2.5 0 00-.9 1.1l-.1.4v.7l.2.5.5.2h.7a2.5 2.5 0 00.8-.3L9 18V18l.4-.4.2-.4.1-.4v-.3-.4a1 1 0 00-.2-.5 1 1 0 00-.4-.2h-.5zm7 0H15a1 1 0 00-.4.3 1 1 0 00-.2.5 1.5 1.5 0 000 .7v.4a2.8 2.8 0 00.5.7h.1a2.8 2.8 0 00.8.6l.4.1h.7l.5-.2.2-.5v-.4-.3a2.6 2.6 0 00-.3-.8 2.4 2.4 0 00-.7-.7 2.5 2.5 0 00-.8-.3h-.3zM12 11.6c-.4 0-.7.1-1 .4-.3.3-.4.6-.4 1s.1.7.4 1c.3.3.6.4 1 .4s.7-.1 1-.4c.3-.3.4-.6.4-1s-.1-.7-.4-1c-.3-.3-.6-.4-1-.4zm8.5-7.1h-11v2h11v-2z"/></svg>',cut:'<svg width="24" height="24"><path d="M18 15c.6.7 1 1.4 1 2.3 0 .8-.2 1.5-.7 2l-.8.5-1 .2c-.4 0-.8 0-1.2-.3a3.9 3.9 0 01-2.1-2.2c-.2-.5-.3-1-.2-1.5l-1-1-1 1c0 .5 0 1-.2 1.5-.1.5-.4 1-.9 1.4-.3.4-.7.6-1.2.8l-1.2.3c-.4 0-.7 0-1-.2-.3 0-.6-.3-.8-.5-.5-.5-.8-1.2-.7-2 0-.9.4-1.6 1-2.2A3.7 3.7 0 018.6 14H9l1-1-4-4-.5-1a3.3 3.3 0 010-2c0-.4.3-.7.5-1l6 6 6-6 .5 1a3.3 3.3 0 010 2c0 .4-.3.7-.5 1l-4 4 1 1h.5c.4 0 .8 0 1.2.3.5.2.9.4 1.2.8zm-8.5 2.2l.1-.4v-.3-.4a1 1 0 00-.2-.5 1 1 0 00-.4-.2 1.6 1.6 0 00-.8 0 2.6 2.6 0 00-.8.3 2.5 2.5 0 00-.9 1.1l-.1.4v.7l.2.5.5.2h.7a2.5 2.5 0 00.8-.3 2.8 2.8 0 001-1zm2.5-2.8c.4 0 .7-.1 1-.4.3-.3.4-.6.4-1s-.1-.7-.4-1c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-.3.3-.4.6-.4 1s.1.7.4 1c.3.3.6.4 1 .4zm5.4 4l.2-.5v-.4-.3a2.6 2.6 0 00-.3-.8 2.4 2.4 0 00-.7-.7 2.5 2.5 0 00-.8-.3 1.5 1.5 0 00-.8 0 1 1 0 00-.4.2 1 1 0 00-.2.5 1.5 1.5 0 000 .7v.4l.3.4.3.4a2.8 2.8 0 00.8.5l.4.1h.7l.5-.2z" fill-rule="evenodd"/></svg>',"document-properties":'<svg width="24" height="24"><path d="M14.4 3H7a2 2 0 00-2 2v14c0 1.1.9 2 2 2h10a2 2 0 002-2V7.6L14.4 3zM17 19H7V5h6v4h4v10z" fill-rule="nonzero"/></svg>',drag:'<svg width="24" height="24"><path d="M13 5h2v2h-2V5zm0 4h2v2h-2V9zM9 9h2v2H9V9zm4 4h2v2h-2v-2zm-4 0h2v2H9v-2zm0 4h2v2H9v-2zm4 0h2v2h-2v-2zM9 5h2v2H9V5z" fill-rule="evenodd"/></svg>',"duplicate-column":'<svg width="24" height="24"><path d="M17 6v16h-7V6h7zm-2 2h-3v12h3V8zm-2-6v2H8v15H6V2h7z"/></svg>',"duplicate-row":'<svg width="24" height="24"><path d="M22 11v7H6v-7h16zm-2 2H8v3h12v-3zm-1-6v2H4v5H2V7h17z"/></svg>',duplicate:'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M16 3v2H6v11H4V5c0-1.1.9-2 2-2h10zm3 8h-2V9h-7v10h9a2 2 0 01-2 2h-7a2 2 0 01-2-2V9c0-1.2.9-2 2-2h7a2 2 0 012 2v2z"/><path d="M17 14h1a1 1 0 010 2h-1v1a1 1 0 01-2 0v-1h-1a1 1 0 010-2h1v-1a1 1 0 012 0v1z"/></g></svg>',"edit-block":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19.8 8.8l-9.4 9.4c-.2.2-.5.4-.9.4l-5.4 1.2 1.2-5.4.5-.8 9.4-9.4c.7-.7 1.8-.7 2.5 0l2.1 2.1c.7.7.7 1.8 0 2.5zm-2-.2l1-.9v-.3l-2.2-2.2a.3.3 0 00-.3 0l-1 1L18 8.5zm-1 1l-2.5-2.4-6 6 2.5 2.5 6-6zm-7 7.1l-2.6-2.4-.3.3-.1.2-.7 3 3.1-.6h.1l.4-.5z"/></svg>',"edit-image":'<svg width="24" height="24"><path d="M18 16h2V7a2 2 0 00-2-2H7v2h11v9zM6 17h15a1 1 0 010 2h-1v1a1 1 0 01-2 0v-1H6a2 2 0 01-2-2V7H3a1 1 0 110-2h1V4a1 1 0 112 0v13zm3-5.3l1.3 2 3-4.7 3.7 6H7l2-3.3z" fill-rule="nonzero"/></svg>',"embed-page":'<svg width="24" height="24"><path d="M19 6V5H5v14h2A13 13 0 0119 6zm0 1.4c-.8.8-1.6 2.4-2.2 4.6H19V7.4zm0 5.6h-2.4c-.4 1.8-.6 3.8-.6 6h3v-6zm-4 6c0-2.2.2-4.2.6-6H13c-.7 1.8-1.1 3.8-1.1 6h3zm-4 0c0-2.2.4-4.2 1-6H9.6A12 12 0 008 19h3zM4 3h16c.6 0 1 .4 1 1v16c0 .6-.4 1-1 1H4a1 1 0 01-1-1V4c0-.6.4-1 1-1zm11.8 9c.4-1.9 1-3.4 1.8-4.5a9.2 9.2 0 00-4 4.5h2.2zm-3.4 0a12 12 0 012.8-4 12 12 0 00-5 4h2.2z" fill-rule="nonzero"/></svg>',embed:'<svg width="24" height="24"><path d="M4 3h16c.6 0 1 .4 1 1v16c0 .6-.4 1-1 1H4a1 1 0 01-1-1V4c0-.6.4-1 1-1zm1 2v14h14V5H5zm4.8 2.6l5.6 4a.5.5 0 010 .8l-5.6 4A.5.5 0 019 16V8a.5.5 0 01.8-.4z" fill-rule="nonzero"/></svg>',emoji:'<svg width="24" height="24"><path d="M9 11c.6 0 1-.4 1-1s-.4-1-1-1a1 1 0 00-1 1c0 .6.4 1 1 1zm6 0c.6 0 1-.4 1-1s-.4-1-1-1a1 1 0 00-1 1c0 .6.4 1 1 1zm-3 5.5c2.1 0 4-1.5 4.4-3.5H7.6c.5 2 2.3 3.5 4.4 3.5zM12 4a8 8 0 100 16 8 8 0 000-16zm0 14.5a6.5 6.5 0 110-13 6.5 6.5 0 010 13z" fill-rule="nonzero"/></svg>',export:'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M14.4 3L18 7v1h-5V5H7v14h9a1 1 0 012 0c0 1-.8 2-1.9 2H7c-1 0-2-.8-2-1.9V5c0-1 .8-2 1.9-2h7.5z"/><path d="M18.1 12c.5 0 .9.4.9 1 0 .5-.3 1-.8 1h-7.3c-.5 0-.9-.4-.9-1 0-.5.3-1 .8-1h7.3z"/><path d="M16.4 9.2a1 1 0 011.4.2l2.4 3.6-2.4 3.6a1 1 0 01-1.7-1v-.2l1.7-2.4-1.6-2.4a1 1 0 01.2-1.4z"/></g></svg>',fill:'<svg width="24" height="26"><path d="M16.6 12l-9-9-1.4 1.4 2.4 2.4-5.2 5.1c-.5.6-.5 1.6 0 2.2L9 19.6a1.5 1.5 0 002.2 0l5.5-5.5c.5-.6.5-1.6 0-2.2zM5.2 13L10 8.2l4.8 4.8H5.2zM19 14.5s-2 2.2-2 3.5c0 1.1.9 2 2 2a2 2 0 002-2c0-1.3-2-3.5-2-3.5z" fill-rule="nonzero"/></svg>',"flip-horizontally":'<svg width="24" height="24"><path d="M14 19h2v-2h-2v2zm4-8h2V9h-2v2zM4 7v10c0 1.1.9 2 2 2h3v-2H6V7h3V5H6a2 2 0 00-2 2zm14-2v2h2a2 2 0 00-2-2zm-7 16h2V3h-2v18zm7-6h2v-2h-2v2zm-4-8h2V5h-2v2zm4 12a2 2 0 002-2h-2v2z" fill-rule="nonzero"/></svg>',"flip-vertically":'<svg width="24" height="24"><path d="M5 14v2h2v-2H5zm8 4v2h2v-2h-2zm4-14H7a2 2 0 00-2 2v3h2V6h10v3h2V6a2 2 0 00-2-2zm2 14h-2v2a2 2 0 002-2zM3 11v2h18v-2H3zm6 7v2h2v-2H9zm8-4v2h2v-2h-2zM5 18c0 1.1.9 2 2 2v-2H5z" fill-rule="nonzero"/></svg>',"format-painter":'<svg width="24" height="24"><path d="M18 5V4c0-.5-.4-1-1-1H5a1 1 0 00-1 1v4c0 .6.5 1 1 1h12c.6 0 1-.4 1-1V7h1v4H9v9c0 .6.4 1 1 1h2c.6 0 1-.4 1-1v-7h8V5h-3z" fill-rule="nonzero"/></svg>',format:'<svg width="24" height="24"><path fill-rule="evenodd" d="M17 5a1 1 0 010 2h-4v11a1 1 0 01-2 0V7H7a1 1 0 110-2h10z"/></svg>',fullscreen:'<svg width="24" height="24"><path d="M15.3 10l-1.2-1.3 2.9-3h-2.3a.9.9 0 110-1.7H19c.5 0 .9.4.9.9v4.4a.9.9 0 11-1.8 0V7l-2.9 3zm0 4l3 3v-2.3a.9.9 0 111.7 0V19c0 .5-.4.9-.9.9h-4.4a.9.9 0 110-1.8H17l-3-2.9 1.3-1.2zM10 15.4l-2.9 3h2.3a.9.9 0 110 1.7H5a.9.9 0 01-.9-.9v-4.4a.9.9 0 111.8 0V17l2.9-3 1.2 1.3zM8.7 10L5.7 7v2.3a.9.9 0 01-1.7 0V5c0-.5.4-.9.9-.9h4.4a.9.9 0 010 1.8H7l3 2.9-1.3 1.2z" fill-rule="nonzero"/></svg>',gallery:'<svg width="24" height="24"><path fill-rule="nonzero" d="M5 15.7l2.3-2.2c.3-.3.7-.3 1 0L11 16l5.1-5c.3-.4.8-.4 1 0l2 1.9V8H5v7.7zM5 18V19h3l1.8-1.9-2-2L5 17.9zm14-3l-2.5-2.4-6.4 6.5H19v-4zM4 6h16c.6 0 1 .4 1 1v13c0 .6-.4 1-1 1H4a1 1 0 01-1-1V7c0-.6.4-1 1-1zm6 7a2 2 0 110-4 2 2 0 010 4zM4.5 4h15a.5.5 0 110 1h-15a.5.5 0 010-1zm2-2h11a.5.5 0 110 1h-11a.5.5 0 010-1z"/></svg>',gamma:'<svg width="24" height="24"><path d="M4 3h16c.6 0 1 .4 1 1v16c0 .6-.4 1-1 1H4a1 1 0 01-1-1V4c0-.6.4-1 1-1zm1 2v14h14V5H5zm6.5 11.8V14L9.2 8.7a5.1 5.1 0 00-.4-.8l-.1-.2H8 8v-1l.3-.1.3-.1h.7a1 1 0 01.6.5l.1.3a8.5 8.5 0 01.3.6l1.9 4.6 2-5.2a1 1 0 011-.6.5.5 0 01.5.6L13 14v2.8a.7.7 0 01-1.4 0z" fill-rule="nonzero"/></svg>',help:'<svg width="24" height="24"><g fill-rule="evenodd"><path d="M12 5.5a6.5 6.5 0 00-6 9 6.3 6.3 0 001.4 2l1 1a6.3 6.3 0 003.6 1 6.5 6.5 0 006-9 6.3 6.3 0 00-1.4-2l-1-1a6.3 6.3 0 00-3.6-1zM12 4a7.8 7.8 0 015.7 2.3A8 8 0 1112 4z"/><path d="M9.6 9.7a.7.7 0 01-.7-.8c0-1.1 1.5-1.8 3.2-1.8 1.8 0 3.2.8 3.2 2.4 0 1.4-.4 2.1-1.5 2.8-.2 0-.3.1-.3.2a2 2 0 00-.8.8.8.8 0 01-1.4-.6c.3-.7.8-1 1.3-1.5l.4-.2c.7-.4.8-.6.8-1.5 0-.5-.6-.9-1.7-.9-.5 0-1 .1-1.4.3-.2 0-.3.1-.3.2v-.2c0 .4-.4.8-.8.8z" fill-rule="nonzero"/><circle cx="12" cy="16" r="1"/></g></svg>',"highlight-bg-color":'<svg width="24" height="24"><g fill-rule="evenodd"><path id="tox-icon-highlight-bg-color__color" d="M3 18h18v3H3z"/><path fill-rule="nonzero" d="M7.7 16.7H3l3.3-3.3-.7-.8L10.2 8l4 4.1-4 4.2c-.2.2-.6.2-.8 0l-.6-.7-1.1 1.1zm5-7.5L11 7.4l3-2.9a2 2 0 012.6 0L18 6c.7.7.7 2 0 2.7l-2.9 2.9-1.8-1.8-.5-.6"/></g></svg>',home:'<svg width="24" height="24"><path fill-rule="nonzero" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>',"horizontal-rule":'<svg width="24" height="24"><path d="M4 11h16v2H4z" fill-rule="evenodd"/></svg>',"image-options":'<svg width="24" height="24"><path d="M6 10a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2zm12 0a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2zm-6 0a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2z" fill-rule="nonzero"/></svg>',image:'<svg width="24" height="24"><path d="M5 15.7l3.3-3.2c.3-.3.7-.3 1 0L12 15l4.1-4c.3-.4.8-.4 1 0l2 1.9V5H5v10.7zM5 18V19h3l2.8-2.9-2-2L5 17.9zm14-3l-2.5-2.4-6.4 6.5H19v-4zM4 3h16c.6 0 1 .4 1 1v16c0 .6-.4 1-1 1H4a1 1 0 01-1-1V4c0-.6.4-1 1-1zm6 8a2 2 0 100-4 2 2 0 000 4z" fill-rule="nonzero"/></svg>',indent:'<svg width="24" height="24"><path d="M7 5h12c.6 0 1 .4 1 1s-.4 1-1 1H7a1 1 0 110-2zm5 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 010-2zm0 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 010-2zm-5 4h12a1 1 0 010 2H7a1 1 0 010-2zm-2.6-3.8L6.2 12l-1.8-1.2a1 1 0 011.2-1.6l3 2a1 1 0 010 1.6l-3 2a1 1 0 11-1.2-1.6z" fill-rule="evenodd"/></svg>',info:'<svg width="24" height="24"><path d="M12 4a7.8 7.8 0 015.7 2.3A8 8 0 1112 4zm-1 3v2h2V7h-2zm3 10v-1h-1v-5h-3v1h1v4h-1v1h4z" fill-rule="evenodd"/></svg>',"insert-character":'<svg width="24" height="24"><path d="M15 18h4l1-2v4h-6v-3.3l1.4-1a6 6 0 001.8-2.9 6.3 6.3 0 00-.1-4.1 5.8 5.8 0 00-3-3.2c-.6-.3-1.3-.5-2.1-.5a5.1 5.1 0 00-3.9 1.8 6.3 6.3 0 00-1.3 6 6.2 6.2 0 001.8 3l1.4.9V20H4v-4l1 2h4v-.5l-2-1L5.4 15A6.5 6.5 0 014 11c0-1 .2-1.9.6-2.7A7 7 0 016.3 6C7.1 5.4 8 5 9 4.5c1-.3 2-.5 3.1-.5a8.8 8.8 0 015.7 2 7 7 0 011.7 2.3 6 6 0 01.2 4.8c-.2.7-.6 1.3-1 1.9a7.6 7.6 0 01-3.6 2.5v.5z" fill-rule="evenodd"/></svg>',"insert-time":'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M12 19a7 7 0 100-14 7 7 0 000 14zm0 2a9 9 0 110-18 9 9 0 010 18z"/><path d="M16 12h-3V7c0-.6-.4-1-1-1a1 1 0 00-1 1v7h5c.6 0 1-.4 1-1s-.4-1-1-1z"/></g></svg>',invert:'<svg width="24" height="24"><path d="M18 19.3L16.5 18a5.8 5.8 0 01-3.1 1.9 6.1 6.1 0 01-5.5-1.6A5.8 5.8 0 016 14v-.3l.1-1.2A13.9 13.9 0 017.7 9l-3-3 .7-.8 2.8 2.9 9 8.9 1.5 1.6-.7.6zm0-5.5v.3l-.1 1.1-.4 1-1.2-1.2a4.3 4.3 0 00.2-1v-.2c0-.4 0-.8-.2-1.3l-.5-1.4a14.8 14.8 0 00-3-4.2L12 6a26.1 26.1 0 00-2.2 2.5l-1-1a20.9 20.9 0 012.9-3.3L12 4l1 .8a22.2 22.2 0 014 5.4c.6 1.2 1 2.4 1 3.6z" fill-rule="evenodd"/></svg>',italic:'<svg width="24" height="24"><path d="M16.7 4.7l-.1.9h-.3c-.6 0-1 0-1.4.3-.3.3-.4.6-.5 1.1l-2.1 9.8v.6c0 .5.4.8 1.4.8h.2l-.2.8H8l.2-.8h.2c1.1 0 1.8-.5 2-1.5l2-9.8.1-.5c0-.6-.4-.8-1.4-.8h-.3l.2-.9h5.8z" fill-rule="evenodd"/></svg>',language:'<svg width="24" height="24"><path d="M12 3a9 9 0 110 18 9 9 0 010-18zm4.3 13.3c-.5 1-1.2 2-2 2.9a7.5 7.5 0 003.2-2.1l-.2-.2a6 6 0 00-1-.6zm-8.6 0c-.5.2-.9.5-1.2.8.9 1 2 1.7 3.2 2a10 10 0 01-2-2.8zm3.6-.8c-.8 0-1.6.1-2.2.3.5 1 1.2 1.9 2.1 2.7zm1.5 0v3c.9-.8 1.6-1.7 2.1-2.7-.6-.2-1.4-.3-2.1-.3zm-6-2.7H4.5c.2 1 .5 2.1 1 3h.3l1.3-1a10 10 0 01-.3-2zm12.7 0h-2.3c0 .7-.1 1.4-.3 2l1.6 1.1c.5-1 .9-2 1-3.1zm-3.8 0h-3V14c1 0 2 .1 2.7.4.2-.5.3-1 .3-1.6zm-4.4 0h-3l.3 1.6c.8-.3 1.7-.4 2.7-.4v-1.3zm-5.5-5c-.7 1-1.1 2.2-1.3 3.5h2.3c0-1 .2-1.8.5-2.6l-1.5-1zm2.9 1.4v.1c-.2.6-.4 1.3-.4 2h3V9.4c-1 0-1.8-.1-2.6-.3zm6.6 0h-.1l-2.4.3v1.8h3l-.5-2.1zm3-1.4l-.3.1-1.3.8c.3.8.5 1.6.5 2.6h2.3a7.5 7.5 0 00-1.3-3.5zm-9 0l2 .2V5.5a9 9 0 00-2 2.2zm3.5-2.3V8c.6 0 1.3 0 1.9-.2a9 9 0 00-2-2.3zm-3-.7h-.1c-1.1.4-2.1 1-3 1.8l1.2.7a10 10 0 011.9-2.5zm4.4 0l.1.1a10 10 0 011.8 2.4l1.1-.7a7.5 7.5 0 00-3-1.8z"/></svg>',"line-height":'<svg width="24" height="24"><path d="M21 5a1 1 0 01.1 2H13a1 1 0 01-.1-2H21zm0 4a1 1 0 01.1 2H13a1 1 0 01-.1-2H21zm0 4a1 1 0 01.1 2H13a1 1 0 01-.1-2H21zm0 4a1 1 0 01.1 2H13a1 1 0 01-.1-2H21zM7 3.6l3.7 3.7a1 1 0 01-1.3 1.5h-.1L8 7.3v9.2l1.3-1.3a1 1 0 011.3 0h.1c.4.4.4 1 0 1.3v.1L7 20.4l-3.7-3.7a1 1 0 011.3-1.5h.1L6 16.7V7.4L4.7 8.7a1 1 0 01-1.3 0h-.1a1 1 0 010-1.3v-.1L7 3.6z"/></svg>',line:'<svg width="24" height="24"><path d="M15 9l-8 8H4v-3l8-8 3 3zm1-1l-3-3 1-1h1c-.2 0 0 0 0 0l2 2s0 .2 0 0v1l-1 1zM4 18h16v2H4v-2z" fill-rule="evenodd"/></svg>',link:'<svg width="24" height="24"><path d="M6.2 12.3a1 1 0 011.4 1.4l-2.1 2a2 2 0 102.7 2.8l4.8-4.8a1 1 0 000-1.4 1 1 0 111.4-1.3 2.9 2.9 0 010 4L9.6 20a3.9 3.9 0 01-5.5-5.5l2-2zm11.6-.6a1 1 0 01-1.4-1.4l2-2a2 2 0 10-2.6-2.8L11 10.3a1 1 0 000 1.4A1 1 0 119.6 13a2.9 2.9 0 010-4L14.4 4a3.9 3.9 0 015.5 5.5l-2 2z" fill-rule="nonzero"/></svg>',"list-bull-circle":'<svg width="48" height="48"><g fill-rule="evenodd"><path d="M11 16a2 2 0 100-4 2 2 0 000 4zm0 1a3 3 0 110-6 3 3 0 010 6zM11 26a2 2 0 100-4 2 2 0 000 4zm0 1a3 3 0 110-6 3 3 0 010 6zM11 36a2 2 0 100-4 2 2 0 000 4zm0 1a3 3 0 110-6 3 3 0 010 6z" fill-rule="nonzero"/><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/></g></svg>',"list-bull-default":'<svg width="48" height="48"><g fill-rule="evenodd"><circle cx="11" cy="14" r="3"/><circle cx="11" cy="24" r="3"/><circle cx="11" cy="34" r="3"/><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/></g></svg>',"list-bull-square":'<svg width="48" height="48"><g fill-rule="evenodd"><path d="M8 11h6v6H8zM8 21h6v6H8zM8 31h6v6H8z"/><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/></g></svg>',"list-num-default-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M37.4 17v-4.8l-1.6 1v-1.1l1.6-1h1.2V17zM33.3 17.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm1.7 5.7c0-1.2 1-2 2.2-2 1.3 0 2.1.8 2.1 1.8 0 .7-.3 1.2-1.3 2.2l-1.2 1v.2h2.6v1h-4.3v-.9l2-1.9c.8-.8 1-1.1 1-1.5 0-.5-.4-.8-1-.8-.5 0-.9.3-.9.9H35zm-1.7 4.3c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm3.2 7.3v-1h.7c.6 0 1-.3 1-.8 0-.4-.4-.7-1-.7s-1 .3-1 .8H35c0-1.1 1-1.8 2.2-1.8 1.2 0 2.1.6 2.1 1.6 0 .7-.4 1.2-1 1.3v.1c.7.1 1.3.7 1.3 1.4 0 1-1 1.9-2.4 1.9-1.3 0-2.2-.8-2.3-2h1.2c0 .6.5 1 1.1 1 .6 0 1-.4 1-1 0-.5-.3-.8-1-.8h-.7zm-3.3 2.7c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .8.3.8.7 0 .4-.3.7-.8.7z"/></g></svg>',"list-num-default":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M10 17v-4.8l-1.5 1v-1.1l1.6-1h1.2V17h-1.2zm3.6.1c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .7.3.7.7 0 .4-.2.7-.7.7zm-5 5.7c0-1.2.8-2 2.1-2s2.1.8 2.1 1.8c0 .7-.3 1.2-1.4 2.2l-1.1 1v.2h2.6v1H8.6v-.9l2-1.9c.8-.8 1-1.1 1-1.5 0-.5-.4-.8-1-.8-.5 0-.9.3-.9.9H8.5zm6.3 4.3c-.5 0-.7-.3-.7-.7 0-.4.2-.7.7-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zM10 34.4v-1h.7c.6 0 1-.3 1-.8 0-.4-.4-.7-1-.7s-1 .3-1 .8H8.6c0-1.1 1-1.8 2.2-1.8 1.3 0 2.1.6 2.1 1.6 0 .7-.4 1.2-1 1.3v.1c.8.1 1.3.7 1.3 1.4 0 1-1 1.9-2.4 1.9-1.3 0-2.2-.8-2.3-2h1.2c0 .6.5 1 1.1 1 .7 0 1-.4 1-1 0-.5-.3-.8-1-.8h-.7zm4.7 2.7c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .8.3.8.7 0 .4-.3.7-.8.7z"/></g></svg>',"list-num-lower-alpha-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M36.5 16c-.9 0-1.5-.5-1.5-1.3s.6-1.3 1.8-1.4h1v-.4c0-.4-.2-.6-.7-.6-.4 0-.7.1-.8.4h-1.1c0-.8.8-1.4 2-1.4S39 12 39 13V16h-1.2v-.6c-.3.4-.8.7-1.4.7zm.4-.8c.6 0 1-.4 1-.9V14h-1c-.5.1-.7.3-.7.6 0 .4.3.6.7.6zM33.1 16.1c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .8.3.8.7 0 .4-.3.7-.8.7zM37.7 26c-.7 0-1.2-.2-1.5-.7v.7H35v-6.3h1.2v2.5c.3-.5.8-.9 1.5-.9 1.1 0 1.8 1 1.8 2.4 0 1.5-.7 2.4-1.8 2.4zm-.5-3.6c-.6 0-1 .5-1 1.3s.4 1.4 1 1.4c.7 0 1-.6 1-1.4 0-.8-.3-1.3-1-1.3zM33.2 26.1c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .8.3.8.7 0 .4-.3.7-.8.7zm6 7h-1c-.1-.5-.4-.8-1-.8s-1 .5-1 1.4c0 1 .4 1.4 1 1.4.5 0 .9-.2 1-.7h1c0 1-.8 1.7-2 1.7-1.4 0-2.2-.9-2.2-2.4s.8-2.4 2.2-2.4c1.2 0 2 .7 2 1.7zm-6.1 3c-.5 0-.7-.3-.7-.7 0-.4.2-.7.7-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-lower-alpha":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M10.3 15.2c.5 0 1-.4 1-.9V14h-1c-.5.1-.8.3-.8.6 0 .4.3.6.8.6zm-.4.9c-1 0-1.5-.6-1.5-1.4 0-.8.6-1.3 1.7-1.4h1.1v-.4c0-.4-.2-.6-.7-.6-.5 0-.8.1-.9.4h-1c0-.8.8-1.4 2-1.4 1.1 0 1.8.6 1.8 1.6V16h-1.1v-.6h-.1c-.2.4-.7.7-1.3.7zm4.6 0c-.5 0-.7-.3-.7-.7 0-.4.2-.7.7-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm-3.2 10c-.6 0-1.2-.3-1.4-.8v.7H8.5v-6.3H10v2.5c.3-.5.8-.9 1.4-.9 1.2 0 1.9 1 1.9 2.4 0 1.5-.7 2.4-1.9 2.4zm-.4-3.7c-.7 0-1 .5-1 1.3s.3 1.4 1 1.4c.6 0 1-.6 1-1.4 0-.8-.4-1.3-1-1.3zm4 3.7c-.5 0-.7-.3-.7-.7 0-.4.2-.7.7-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm-2.2 7h-1.2c0-.5-.4-.8-.9-.8-.6 0-1 .5-1 1.4 0 1 .4 1.4 1 1.4.5 0 .8-.2 1-.7h1c0 1-.8 1.7-2 1.7-1.4 0-2.2-.9-2.2-2.4s.8-2.4 2.2-2.4c1.2 0 2 .7 2 1.7zm1.8 3c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-lower-greek-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M37.4 16c-1.2 0-2-.8-2-2.3 0-1.5.8-2.4 2-2.4.6 0 1 .4 1.3 1v-.9H40v3.2c0 .4.1.5.4.5h.2v.9h-.6c-.6 0-1-.2-1-.7h-.2c-.2.4-.7.8-1.3.8zm.3-1c.6 0 1-.5 1-1.3s-.4-1.3-1-1.3-1 .5-1 1.3.4 1.4 1 1.4zM33.3 16.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zM36 21.9c0-1.5.8-2.3 2.1-2.3 1.2 0 2 .6 2 1.6 0 .6-.3 1-.9 1.3.9.3 1.3.8 1.3 1.7 0 1.2-.7 1.9-1.8 1.9-.6 0-1.1-.3-1.4-.8v2.2H36V22zm1.8 1.2v-1h.3c.5 0 .9-.2.9-.7 0-.5-.3-.8-.9-.8-.5 0-.8.3-.8 1v2.2c0 .8.4 1.3 1 1.3s1-.4 1-1-.4-1-1.2-1h-.3zM33.3 26.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zM37.1 34.6L34.8 30h1.4l1.7 3.5 1.7-3.5h1.1l-2.2 4.6v.1c.5.8.7 1.4.7 1.8 0 .4-.2.8-.4 1-.2.2-.6.3-1 .3-.9 0-1.3-.4-1.3-1.2 0-.5.2-1 .5-1.7l.1-.2zm.7 1a2 2 0 00-.4.9c0 .3.1.4.4.4.3 0 .4-.1.4-.4 0-.2-.1-.6-.4-1zM33.3 36.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-lower-greek":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M10.5 15c.7 0 1-.5 1-1.3s-.3-1.3-1-1.3c-.5 0-.9.5-.9 1.3s.4 1.4 1 1.4zm-.3 1c-1.1 0-1.8-.8-1.8-2.3 0-1.5.7-2.4 1.8-2.4.7 0 1.1.4 1.3 1h.1v-.9h1.2v3.2c0 .4.1.5.4.5h.2v.9h-.6c-.6 0-1-.2-1.1-.7h-.1c-.2.4-.7.8-1.4.8zm5 .1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.7-.7.5 0 .8.3.8.7 0 .4-.3.7-.8.7zm-4.9 7v-1h.3c.6 0 1-.2 1-.7 0-.5-.4-.8-1-.8-.5 0-.8.3-.8 1v2.2c0 .8.4 1.3 1.1 1.3.6 0 1-.4 1-1s-.5-1-1.3-1h-.3zM8.6 22c0-1.5.7-2.3 2-2.3 1.2 0 2 .6 2 1.6 0 .6-.3 1-.8 1.3.8.3 1.3.8 1.3 1.7 0 1.2-.8 1.9-1.9 1.9-.6 0-1.1-.3-1.3-.8v2.2H8.5V22zm6.2 4.2c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .7.3.7.7 0 .4-.2.7-.7.7zm-4.5 8.5L8 30h1.4l1.7 3.5 1.7-3.5h1.1l-2.2 4.6v.1c.5.8.7 1.4.7 1.8 0 .4-.1.8-.4 1-.2.2-.6.3-1 .3-.9 0-1.3-.4-1.3-1.2 0-.5.2-1 .5-1.7l.1-.2zm.7 1a2 2 0 00-.4.9c0 .3.1.4.4.4.3 0 .4-.1.4-.4 0-.2-.1-.6-.4-1zm4.5.5c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-lower-roman-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M32.9 16v-1.2h-1.3V16H33zm0 10v-1.2h-1.3V26H33zm0 10v-1.2h-1.3V36H33z"/><path fill-rule="nonzero" d="M36 21h-1.5v5H36zM36 31h-1.5v5H36zM39 21h-1.5v5H39zM39 31h-1.5v5H39zM42 31h-1.5v5H42zM36 11h-1.5v5H36zM36 19h-1.5v1H36zM36 29h-1.5v1H36zM39 19h-1.5v1H39zM39 29h-1.5v1H39zM42 29h-1.5v1H42zM36 9h-1.5v1H36z"/></g></svg>',"list-num-lower-roman":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M15.1 16v-1.2h1.3V16H15zm0 10v-1.2h1.3V26H15zm0 10v-1.2h1.3V36H15z"/><path fill-rule="nonzero" d="M12 21h1.5v5H12zM12 31h1.5v5H12zM9 21h1.5v5H9zM9 31h1.5v5H9zM6 31h1.5v5H6zM12 11h1.5v5H12zM12 19h1.5v1H12zM12 29h1.5v1H12zM9 19h1.5v1H9zM9 29h1.5v1H9zM6 29h1.5v1H6zM12 9h1.5v1H12z"/></g></svg>',"list-num-upper-alpha-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M39.3 17l-.5-1.4h-2l-.5 1.4H35l2-6h1.6l2 6h-1.3zm-1.6-4.7l-.7 2.3h1.6l-.8-2.3zM33.4 17c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .7.3.7.7 0 .4-.2.7-.7.7zm4.7 9.9h-2.7v-6H38c1.2 0 1.9.6 1.9 1.5 0 .6-.5 1.2-1 1.3.7.1 1.3.7 1.3 1.5 0 1-.8 1.7-2 1.7zm-1.4-5v1.5h1c.6 0 1-.3 1-.8 0-.4-.4-.7-1-.7h-1zm0 4h1.1c.7 0 1.1-.3 1.1-.8 0-.6-.4-.9-1.1-.9h-1.1V26zM33 27.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm4.9 10c-1.8 0-2.8-1.1-2.8-3.1s1-3.1 2.8-3.1c1.4 0 2.5.9 2.6 2.2h-1.3c0-.7-.6-1.1-1.3-1.1-1 0-1.6.7-1.6 2s.6 2 1.6 2c.7 0 1.2-.4 1.4-1h1.2c-.1 1.3-1.2 2.2-2.6 2.2zm-4.5 0c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-upper-alpha":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M12.6 17l-.5-1.4h-2L9.5 17H8.3l2-6H12l2 6h-1.3zM11 12.3l-.7 2.3h1.6l-.8-2.3zm4.7 4.8c-.4 0-.7-.3-.7-.7 0-.4.3-.7.7-.7.5 0 .7.3.7.7 0 .4-.2.7-.7.7zM11.4 27H8.7v-6h2.6c1.2 0 1.9.6 1.9 1.5 0 .6-.5 1.2-1 1.3.7.1 1.3.7 1.3 1.5 0 1-.8 1.7-2 1.7zM10 22v1.5h1c.6 0 1-.3 1-.8 0-.4-.4-.7-1-.7h-1zm0 4H11c.7 0 1.1-.3 1.1-.8 0-.6-.4-.9-1.1-.9H10V26zm5.4 1.1c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7zm-4.1 10c-1.8 0-2.8-1.1-2.8-3.1s1-3.1 2.8-3.1c1.4 0 2.5.9 2.6 2.2h-1.3c0-.7-.6-1.1-1.3-1.1-1 0-1.6.7-1.6 2s.6 2 1.6 2c.7 0 1.2-.4 1.4-1h1.2c-.1 1.3-1.2 2.2-2.6 2.2zm4.5 0c-.5 0-.8-.3-.8-.7 0-.4.3-.7.8-.7.4 0 .7.3.7.7 0 .4-.3.7-.7.7z"/></g></svg>',"list-num-upper-roman-rtl":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M8 12h22v4H8zM8 22h22v4H8zM8 32h22v4H8z"/><path d="M31.6 17v-1.2H33V17h-1.3zm0 10v-1.2H33V27h-1.3zm0 10v-1.2H33V37h-1.3z"/><path fill-rule="nonzero" d="M34.5 20H36v7h-1.5zM34.5 30H36v7h-1.5zM37.5 20H39v7h-1.5zM37.5 30H39v7h-1.5zM40.5 30H42v7h-1.5zM34.5 10H36v7h-1.5z"/></g></svg>',"list-num-upper-roman":'<svg width="48" height="48"><g fill-rule="evenodd"><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"/><path d="M15.1 17v-1.2h1.3V17H15zm0 10v-1.2h1.3V27H15zm0 10v-1.2h1.3V37H15z"/><path fill-rule="nonzero" d="M12 20h1.5v7H12zM12 30h1.5v7H12zM9 20h1.5v7H9zM9 30h1.5v7H9zM6 30h1.5v7H6zM12 10h1.5v7H12z"/></g></svg>',lock:'<svg width="24" height="24"><path d="M16.3 11c.2 0 .3 0 .5.2l.2.6v7.4c0 .3 0 .4-.2.6l-.6.2H7.8c-.3 0-.4 0-.6-.2a.7.7 0 01-.2-.6v-7.4c0-.3 0-.4.2-.6l.5-.2H8V8c0-.8.3-1.5.9-2.1.6-.6 1.3-.9 2.1-.9h2c.8 0 1.5.3 2.1.9.6.6.9 1.3.9 2.1v3h.3zM10 8v3h4V8a1 1 0 00-.3-.7A1 1 0 0013 7h-2a1 1 0 00-.7.3 1 1 0 00-.3.7z" fill-rule="evenodd"/></svg>',ltr:'<svg width="24" height="24"><path d="M11 5h7a1 1 0 010 2h-1v11a1 1 0 01-2 0V7h-2v11a1 1 0 01-2 0v-6c-.5 0-1 0-1.4-.3A3.4 3.4 0 017.8 10a3.3 3.3 0 010-2.8 3.4 3.4 0 011.8-1.8L11 5zM4.4 16.2L6.2 15l-1.8-1.2a1 1 0 011.2-1.6l3 2a1 1 0 010 1.6l-3 2a1 1 0 11-1.2-1.6z" fill-rule="evenodd"/></svg>',"more-drawer":'<svg width="24" height="24"><path d="M6 10a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2zm12 0a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2zm-6 0a2 2 0 00-2 2c0 1.1.9 2 2 2a2 2 0 002-2 2 2 0 00-2-2z" fill-rule="nonzero"/></svg>',"new-document":'<svg width="24" height="24"><path d="M14.4 3H7a2 2 0 00-2 2v14c0 1.1.9 2 2 2h10a2 2 0 002-2V7.6L14.4 3zM17 19H7V5h6v4h4v10z" fill-rule="nonzero"/></svg>',"new-tab":'<svg width="24" height="24"><path d="M15 13l2-2v8H5V7h8l-2 2H7v8h8v-4zm4-8v5.5l-2-2-5.6 5.5H10v-1.4L15.5 7l-2-2H19z" fill-rule="evenodd"/></svg>',"non-breaking":'<svg width="24" height="24"><path d="M11 11H8a1 1 0 110-2h3V6c0-.6.4-1 1-1s1 .4 1 1v3h3c.6 0 1 .4 1 1s-.4 1-1 1h-3v3c0 .6-.4 1-1 1a1 1 0 01-1-1v-3zm10 4v5H3v-5c0-.6.4-1 1-1s1 .4 1 1v3h14v-3c0-.6.4-1 1-1s1 .4 1 1z" fill-rule="evenodd"/></svg>',notice:'<svg width="24" height="24"><path d="M17.8 9.8L15.4 4 20 8.5v7L15.5 20h-7L4 15.5v-7L8.5 4h7l2.3 5.8zm0 0l2.2 5.7-2.3-5.8zM13 17v-2h-2v2h2zm0-4V7h-2v6h2z" fill-rule="evenodd"/></svg>',"ordered-list-rtl":'<svg width="24" height="24"><path d="M6 17h8a1 1 0 010 2H6a1 1 0 010-2zm0-6h8a1 1 0 010 2H6a1 1 0 010-2zm0-6h8a1 1 0 010 2H6a1 1 0 110-2zm13-1v3.5a.5.5 0 11-1 0V5h-.5a.5.5 0 110-1H19zm-1 8.8l.2.2h1.3a.5.5 0 110 1h-1.6a1 1 0 01-.9-1V13c0-.4.3-.8.6-1l1.2-.4.2-.3a.2.2 0 00-.2-.2h-1.3a.5.5 0 01-.5-.5c0-.3.2-.5.5-.5h1.6c.5 0 .9.4.9 1v.1c0 .4-.3.8-.6 1l-1.2.4-.2.3zm2 4.2v2c0 .6-.4 1-1 1h-1.5a.5.5 0 010-1h1.2a.3.3 0 100-.6h-1.3a.4.4 0 110-.8h1.3a.3.3 0 000-.6h-1.2a.5.5 0 110-1H19c.6 0 1 .4 1 1z" fill-rule="evenodd"/></svg>',"ordered-list":'<svg width="24" height="24"><path d="M10 17h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 110-2zM6 4v3.5c0 .3-.2.5-.5.5a.5.5 0 01-.5-.5V5h-.5a.5.5 0 010-1H6zm-1 8.8l.2.2h1.3c.3 0 .5.2.5.5s-.2.5-.5.5H4.9a1 1 0 01-.9-1V13c0-.4.3-.8.6-1l1.2-.4.2-.3a.2.2 0 00-.2-.2H4.5a.5.5 0 01-.5-.5c0-.3.2-.5.5-.5h1.6c.5 0 .9.4.9 1v.1c0 .4-.3.8-.6 1l-1.2.4-.2.3zM7 17v2c0 .6-.4 1-1 1H4.5a.5.5 0 010-1h1.2c.2 0 .3-.1.3-.3 0-.2-.1-.3-.3-.3H4.4a.4.4 0 110-.8h1.3c.2 0 .3-.1.3-.3 0-.2-.1-.3-.3-.3H4.5a.5.5 0 110-1H6c.6 0 1 .4 1 1z" fill-rule="evenodd"/></svg>',orientation:'<svg width="24" height="24"><path d="M7.3 6.4L1 13l6.4 6.5 6.5-6.5-6.5-6.5zM3.7 13l3.6-3.7L11 13l-3.7 3.7-3.6-3.7zM12 6l2.8 2.7c.3.3.3.8 0 1-.3.4-.9.4-1.2 0L9.2 5.7a.8.8 0 010-1.2L13.6.2c.3-.3.9-.3 1.2 0 .3.3.3.8 0 1.1L12 4h1a9 9 0 11-4.3 16.9l1.5-1.5A7 7 0 1013 6h-1z" fill-rule="nonzero"/></svg>',outdent:'<svg width="24" height="24"><path d="M7 5h12c.6 0 1 .4 1 1s-.4 1-1 1H7a1 1 0 110-2zm5 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 010-2zm0 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 010-2zm-5 4h12a1 1 0 010 2H7a1 1 0 010-2zm1.6-3.8a1 1 0 01-1.2 1.6l-3-2a1 1 0 010-1.6l3-2a1 1 0 011.2 1.6L6.8 12l1.8 1.2z" fill-rule="evenodd"/></svg>',"page-break":'<svg width="24" height="24"><g fill-rule="evenodd"><path d="M5 11c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 010-2zm3 0h1c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 010-2zm4 0c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 010-2zm3 0h1c.6 0 1 .4 1 1s-.4 1-1 1h-1a1 1 0 010-2zm4 0c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 010-2zM7 3v5h10V3c0-.6.4-1 1-1s1 .4 1 1v7H5V3c0-.6.4-1 1-1s1 .4 1 1zM6 22a1 1 0 01-1-1v-7h14v7c0 .6-.4 1-1 1a1 1 0 01-1-1v-5H7v5c0 .6-.4 1-1 1z"/></g></svg>',paragraph:'<svg width="24" height="24"><path fill-rule="evenodd" d="M10 5h7a1 1 0 010 2h-1v11a1 1 0 01-2 0V7h-2v11a1 1 0 01-2 0v-6c-.5 0-1 0-1.4-.3A3.4 3.4 0 016.8 10a3.3 3.3 0 010-2.8 3.4 3.4 0 011.8-1.8L10 5z"/></svg>',"paste-column-after":'<svg width="24" height="24"><path fill-rule="evenodd" d="M12 1a3 3 0 012.8 2H18c1 0 2 .8 2 1.9V7h-2V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v13h7v2H6c-1 0-2-.8-2-1.9V5c0-1 .8-2 1.9-2H9.2A3 3 0 0112 1zm8 7v12h-6V8h6zm-1.5 1.5h-3v9h3v-9zM12 3a1 1 0 100 2 1 1 0 000-2z"/></svg>',"paste-column-before":'<svg width="24" height="24"><path fill-rule="evenodd" d="M12 1a3 3 0 012.8 2H18c1 0 2 .8 2 1.9V18c0 1-.8 2-1.9 2H11v-2h7V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v2H4V5c0-1 .8-2 1.9-2H9.2A3 3 0 0112 1zm-2 7v12H4V8h6zM8.5 9.5h-3v9h3v-9zM12 3a1 1 0 100 2 1 1 0 000-2z"/></svg>',"paste-row-after":'<svg width="24" height="24"><path fill-rule="evenodd" d="M12 1a3 3 0 012.8 2H18c1 0 2 .8 2 1.9V11h-2V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v13h14c0 1-.8 2-1.9 2H6c-1 0-2-.8-2-1.9V5c0-1 .8-2 1.9-2H9.2A3 3 0 0112 1zm10 11v5H8v-5h14zm-1.5 1.5h-11v2h11v-2zM12 3a1 1 0 100 2 1 1 0 000-2z"/></svg>',"paste-row-before":'<svg width="24" height="24"><path fill-rule="evenodd" d="M12 1a3 3 0 012.8 2H18c1 0 2 .8 2 1.9V7h-2V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v13h12v-4h2v4c0 1-.8 2-1.9 2H6c-1 0-2-.8-2-1.9V5c0-1 .8-2 1.9-2H9.2A3 3 0 0112 1zm10 7v5H8V8h14zm-1.5 1.5h-11v2h11v-2zM12 3a1 1 0 100 2 1 1 0 000-2z"/></svg>',"paste-text":'<svg width="24" height="24"><path d="M18 9V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v13h3V9h9zM9 20H6a2 2 0 01-2-2V5c0-1.1.9-2 2-2h3.2A3 3 0 0112 1a3 3 0 012.8 2H18a2 2 0 012 2v4h1v12H9v-1zm1.5-9.5v9h9v-9h-9zM12 3a1 1 0 00-1 1c0 .5.4 1 1 1s1-.5 1-1-.4-1-1-1zm0 9h6v2h-.5l-.5-1h-1v4h.8v1h-3.6v-1h.8v-4h-1l-.5 1H12v-2z" fill-rule="nonzero"/></svg>',paste:'<svg width="24" height="24"><path d="M18 9V5h-2v1c0 .6-.4 1-1 1H9a1 1 0 01-1-1V5H6v13h3V9h9zM9 20H6a2 2 0 01-2-2V5c0-1.1.9-2 2-2h3.2A3 3 0 0112 1a3 3 0 012.8 2H18a2 2 0 012 2v4h1v12H9v-1zm1.5-9.5v9h9v-9h-9zM12 3a1 1 0 00-1 1c0 .5.4 1 1 1s1-.5 1-1-.4-1-1-1z" fill-rule="nonzero"/></svg>',"permanent-pen":'<svg width="24" height="24"><path d="M10.5 17.5L8 20H3v-3l3.5-3.5a2 2 0 010-3L14 3l1 1-7.3 7.3a1 1 0 000 1.4l3.6 3.6c.4.4 1 .4 1.4 0L20 9l1 1-7.6 7.6a2 2 0 01-2.8 0l-.1-.1z" fill-rule="nonzero"/></svg>',plus:'<svg width="24" height="24"><path d="M12 4c.5 0 1 .4 1 .9V11h6a1 1 0 01.1 2H13v6a1 1 0 01-2 .1V13H5a1 1 0 01-.1-2H11V5c0-.6.4-1 1-1z"/></svg>',preferences:'<svg width="24" height="24"><path d="M20.1 13.5l-1.9.2a5.8 5.8 0 01-.6 1.5l1.2 1.5c.4.4.3 1 0 1.4l-.7.7a1 1 0 01-1.4 0l-1.5-1.2a6.2 6.2 0 01-1.5.6l-.2 1.9c0 .5-.5.9-1 .9h-1a1 1 0 01-1-.9l-.2-1.9a5.8 5.8 0 01-1.5-.6l-1.5 1.2a1 1 0 01-1.4 0l-.7-.7a1 1 0 010-1.4l1.2-1.5a6.2 6.2 0 01-.6-1.5l-1.9-.2a1 1 0 01-.9-1v-1c0-.5.4-1 .9-1l1.9-.2a5.8 5.8 0 01.6-1.5L5.2 7.3a1 1 0 010-1.4l.7-.7a1 1 0 011.4 0l1.5 1.2a6.2 6.2 0 011.5-.6l.2-1.9c0-.5.5-.9 1-.9h1c.5 0 1 .4 1 .9l.2 1.9a5.8 5.8 0 011.5.6l1.5-1.2a1 1 0 011.4 0l.7.7c.3.4.4 1 0 1.4l-1.2 1.5a6.2 6.2 0 01.6 1.5l1.9.2c.5 0 .9.5.9 1v1c0 .5-.4 1-.9 1zM12 15a3 3 0 100-6 3 3 0 000 6z" fill-rule="evenodd"/></svg>',preview:'<svg width="24" height="24"><path d="M3.5 12.5c.5.8 1.1 1.6 1.8 2.3 2 2 4.2 3.2 6.7 3.2s4.7-1.2 6.7-3.2a16.2 16.2 0 002.1-2.8 15.7 15.7 0 00-2.1-2.8c-2-2-4.2-3.2-6.7-3.2a9.3 9.3 0 00-6.7 3.2A16.2 16.2 0 003.2 12c0 .2.2.3.3.5zm-2.4-1l.7-1.2L4 7.8C6.2 5.4 8.9 4 12 4c3 0 5.8 1.4 8.1 3.8a18.2 18.2 0 012.8 3.7v1l-.7 1.2-2.1 2.5c-2.3 2.4-5 3.8-8.1 3.8-3 0-5.8-1.4-8.1-3.8a18.2 18.2 0 01-2.8-3.7 1 1 0 010-1zm12-3.3a2 2 0 102.7 2.6 4 4 0 11-2.6-2.6z" fill-rule="nonzero"/></svg>',print:'<svg width="24" height="24"><path d="M18 8H6a3 3 0 00-3 3v6h2v3h14v-3h2v-6a3 3 0 00-3-3zm-1 10H7v-4h10v4zm.5-5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm.5-8H6v2h12V5z" fill-rule="nonzero"/></svg>',quote:'<svg width="24" height="24"><path d="M7.5 17h.9c.4 0 .7-.2.9-.6L11 13V8c0-.6-.4-1-1-1H6a1 1 0 00-1 1v4c0 .6.4 1 1 1h2l-1.3 2.7a1 1 0 00.8 1.3zm8 0h.9c.4 0 .7-.2.9-.6L19 13V8c0-.6-.4-1-1-1h-4a1 1 0 00-1 1v4c0 .6.4 1 1 1h2l-1.3 2.7a1 1 0 00.8 1.3z" fill-rule="nonzero"/></svg>',redo:'<svg width="24" height="24"><path d="M17.6 10H12c-2.8 0-4.4 1.4-4.9 3.5-.4 2 .3 4 1.4 4.6a1 1 0 11-1 1.8c-2-1.2-2.9-4.1-2.3-6.8.6-3 3-5.1 6.8-5.1h5.6l-3.3-3.3a1 1 0 111.4-1.4l5 5a1 1 0 010 1.4l-5 5a1 1 0 01-1.4-1.4l3.3-3.3z" fill-rule="nonzero"/></svg>',reload:'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M5 22.1l-1.2-4.7v-.2a1 1 0 011-1l5 .4a1 1 0 11-.2 2l-2.2-.2a7.8 7.8 0 008.4.2 7.5 7.5 0 003.5-6.4 1 1 0 112 0 9.5 9.5 0 01-4.5 8 9.9 9.9 0 01-10.2 0l.4 1.4a1 1 0 11-2 .5zM13.6 7.4c0-.5.5-1 1-.9l2.8.2a8 8 0 00-9.5-1 7.5 7.5 0 00-3.6 7 1 1 0 01-2 0 9.5 9.5 0 014.5-8.6 10 10 0 0110.9.3l-.3-1a1 1 0 012-.5l1.1 4.8a1 1 0 01-1 1.2l-5-.4a1 1 0 01-.9-1z"/></g></svg>',"remove-formatting":'<svg width="24" height="24"><path d="M13.2 6a1 1 0 010 .2l-2.6 10a1 1 0 01-1 .8h-.2a.8.8 0 01-.8-1l2.6-10H8a1 1 0 110-2h9a1 1 0 010 2h-3.8zM5 18h7a1 1 0 010 2H5a1 1 0 010-2zm13 1.5L16.5 18 15 19.5a.7.7 0 01-1-1l1.5-1.5-1.5-1.5a.7.7 0 011-1l1.5 1.5 1.5-1.5a.7.7 0 011 1L17.5 17l1.5 1.5a.7.7 0 01-1 1z" fill-rule="evenodd"/></svg>',remove:'<svg width="24" height="24"><path d="M16 7h3a1 1 0 010 2h-1v9a3 3 0 01-3 3H9a3 3 0 01-3-3V9H5a1 1 0 110-2h3V6a3 3 0 013-3h2a3 3 0 013 3v1zm-2 0V6c0-.6-.4-1-1-1h-2a1 1 0 00-1 1v1h4zm2 2H8v9c0 .6.4 1 1 1h6c.6 0 1-.4 1-1V9zm-7 3a1 1 0 012 0v4a1 1 0 01-2 0v-4zm4 0a1 1 0 012 0v4a1 1 0 01-2 0v-4z" fill-rule="nonzero"/></svg>',"resize-handle":'<svg width="10" height="10"><g fill-rule="nonzero"><path d="M8.1 1.1A.5.5 0 119 2l-7 7A.5.5 0 111 8l7-7zM8.1 5.1A.5.5 0 119 6l-3 3A.5.5 0 115 8l3-3z"/></g></svg>',resize:'<svg width="24" height="24"><path d="M4 5c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h6c.3 0 .5.1.7.3.2.2.3.4.3.7 0 .3-.1.5-.3.7a1 1 0 01-.7.3H7.4L18 16.6V13c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3.3 0 .5.1.7.3.2.2.3.4.3.7v6c0 .3-.1.5-.3.7a1 1 0 01-.7.3h-6a1 1 0 01-.7-.3 1 1 0 01-.3-.7c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h3.6L6 7.4V11c0 .3-.1.5-.3.7a1 1 0 01-.7.3 1 1 0 01-.7-.3A1 1 0 014 11V5z" fill-rule="evenodd"/></svg>',"restore-draft":'<svg width="24" height="24"><g fill-rule="evenodd"><path d="M17 13c0 .6-.4 1-1 1h-4V8c0-.6.4-1 1-1s1 .4 1 1v4h2c.6 0 1 .4 1 1z"/><path d="M4.7 10H9a1 1 0 010 2H3a1 1 0 01-1-1V5a1 1 0 112 0v3l2.5-2.4a9.2 9.2 0 0110.8-1.5A9 9 0 0113.4 21c-2.4.1-4.7-.7-6.5-2.2a1 1 0 111.3-1.5 7.2 7.2 0 0011.6-3.7 7 7 0 00-3.5-7.7A7.2 7.2 0 008 7L4.7 10z" fill-rule="nonzero"/></g></svg>',"rotate-left":'<svg width="24" height="24"><path d="M4.7 10H9a1 1 0 010 2H3a1 1 0 01-1-1V5a1 1 0 112 0v3l2.5-2.4a9.2 9.2 0 0110.8-1.5A9 9 0 0113.4 21c-2.4.1-4.7-.7-6.5-2.2a1 1 0 111.3-1.5 7.2 7.2 0 0011.6-3.7 7 7 0 00-3.5-7.7A7.2 7.2 0 008 7L4.7 10z" fill-rule="nonzero"/></svg>',"rotate-right":'<svg width="24" height="24"><path d="M20 8V5a1 1 0 012 0v6c0 .6-.4 1-1 1h-6a1 1 0 010-2h4.3L16 7A7.2 7.2 0 007.7 6a7 7 0 003 13.1c1.9.1 3.7-.5 5-1.7a1 1 0 011.4 1.5A9.2 9.2 0 012.2 14c-.9-3.9 1-8 4.5-9.9 3.5-1.9 8-1.3 10.8 1.5L20 8z" fill-rule="nonzero"/></svg>',rtl:'<svg width="24" height="24"><path d="M8 5h8v2h-2v12h-2V7h-2v12H8v-7c-.5 0-1 0-1.4-.3A3.4 3.4 0 014.8 10a3.3 3.3 0 010-2.8 3.4 3.4 0 011.8-1.8L8 5zm12 11.2a1 1 0 11-1 1.6l-3-2a1 1 0 010-1.6l3-2a1 1 0 111 1.6L18.4 15l1.8 1.2z" fill-rule="evenodd"/></svg>',save:'<svg width="24" height="24"><path d="M5 16h14a2 2 0 012 2v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-2c0-1.1.9-2 2-2zm0 2v2h14v-2H5zm10 0h2v2h-2v-2zm-4-6.4L8.7 9.3a1 1 0 10-1.4 1.4l4 4c.4.4 1 .4 1.4 0l4-4a1 1 0 10-1.4-1.4L13 11.6V4a1 1 0 00-2 0v7.6z" fill-rule="nonzero"/></svg>',search:'<svg width="24" height="24"><path d="M16 17.3a8 8 0 111.4-1.4l4.3 4.4a1 1 0 01-1.4 1.4l-4.4-4.3zm-5-.3a6 6 0 100-12 6 6 0 000 12z" fill-rule="nonzero"/></svg>',"select-all":'<svg width="24" height="24"><path d="M3 5h2V3a2 2 0 00-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2a2 2 0 00-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8a2 2 0 002-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z" fill-rule="nonzero"/></svg>',selected:'<svg width="24" height="24"><path fill-rule="nonzero" d="M6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6c0-1.1.9-2 2-2zm3.6 10.9L7 12.3a.7.7 0 00-1 1L9.6 17 18 8.6a.7.7 0 000-1 .7.7 0 00-1 0l-7.4 7.3z"/></svg>',settings:'<svg width="24" height="24"><path d="M11 6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8v.3c0 .2 0 .3-.2.5l-.6.2H7.8c-.3 0-.4 0-.6-.2a.7.7 0 01-.2-.6V8H5a1 1 0 110-2h2v-.3c0-.2 0-.3.2-.5l.5-.2h2.5c.3 0 .4 0 .6.2l.2.5V6zM8 8h2V6H8v2zm9 2.8v.2h2c.6 0 1 .4 1 1s-.4 1-1 1h-2v.3c0 .2 0 .3-.2.5l-.6.2h-2.4c-.3 0-.4 0-.6-.2a.7.7 0 01-.2-.6V13H5a1 1 0 010-2h8v-.3c0-.2 0-.3.2-.5l.6-.2h2.4c.3 0 .4 0 .6.2l.2.6zM14 13h2v-2h-2v2zm-3 2.8v.2h8c.6 0 1 .4 1 1s-.4 1-1 1h-8v.3c0 .2 0 .3-.2.5l-.6.2H7.8c-.3 0-.4 0-.6-.2a.7.7 0 01-.2-.6V18H5a1 1 0 010-2h2v-.3c0-.2 0-.3.2-.5l.5-.2h2.5c.3 0 .4 0 .6.2l.2.6zM8 18h2v-2H8v2z" fill-rule="evenodd"/></svg>',sharpen:'<svg width="24" height="24"><path d="M16 6l4 4-8 9-8-9 4-4h8zm-4 10.2l5.5-6.2-.1-.1H12v-.3h5.1l-.2-.2H12V9h4.6l-.2-.2H12v-.3h4.1l-.2-.2H12V8h3.6l-.2-.2H8.7L6.5 10l.1.1H12v.3H6.9l.2.2H12v.3H7.3l.2.2H12v.3H7.7l.3.2h4v.3H8.2l.2.2H12v.3H8.6l.3.2H12v.3H9l.3.2H12v.3H9.5l.2.2H12v.3h-2l.2.2H12v.3h-1.6l.2.2H12v.3h-1.1l.2.2h.9v.3h-.7l.2.2h.5v.3h-.3l.3.2z" fill-rule="evenodd"/></svg>',sourcecode:'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M9.8 15.7c.3.3.3.8 0 1-.3.4-.9.4-1.2 0l-4.4-4.1a.8.8 0 010-1.2l4.4-4.2c.3-.3.9-.3 1.2 0 .3.3.3.8 0 1.1L6 12l3.8 3.7zM14.2 15.7c-.3.3-.3.8 0 1 .4.4.9.4 1.2 0l4.4-4.1c.3-.3.3-.9 0-1.2l-4.4-4.2a.8.8 0 00-1.2 0c-.3.3-.3.8 0 1.1L18 12l-3.8 3.7z"/></g></svg>',"spell-check":'<svg width="24" height="24"><path d="M6 8v3H5V5c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h2c.3 0 .5.1.7.3.2.2.3.4.3.7v6H8V8H6zm0-3v2h2V5H6zm13 0h-3v5h3v1h-3a1 1 0 01-.7-.3 1 1 0 01-.3-.7V5c0-.3.1-.5.3-.7.2-.2.4-.3.7-.3h3v1zm-5 1.5l-.1.7c-.1.2-.3.3-.6.3.3 0 .5.1.6.3l.1.7V10c0 .3-.1.5-.3.7a1 1 0 01-.7.3h-3V4h3c.3 0 .5.1.7.3.2.2.3.4.3.7v1.5zM13 10V8h-2v2h2zm0-3V5h-2v2h2zm3 5l1 1-6.5 7L7 15.5l1.3-1 2.2 2.2L16 12z" fill-rule="evenodd"/></svg>',"strike-through":'<svg width="24" height="24"><g fill-rule="evenodd"><path d="M15.6 8.5c-.5-.7-1-1.1-1.3-1.3-.6-.4-1.3-.6-2-.6-2.7 0-2.8 1.7-2.8 2.1 0 1.6 1.8 2 3.2 2.3 4.4.9 4.6 2.8 4.6 3.9 0 1.4-.7 4.1-5 4.1A6.2 6.2 0 017 16.4l1.5-1.1c.4.6 1.6 2 3.7 2 1.6 0 2.5-.4 3-1.2.4-.8.3-2-.8-2.6-.7-.4-1.6-.7-2.9-1-1-.2-3.9-.8-3.9-3.6C7.6 6 10.3 5 12.4 5c2.9 0 4.2 1.6 4.7 2.4l-1.5 1.1z"/><path d="M5 11h14a1 1 0 010 2H5a1 1 0 010-2z" fill-rule="nonzero"/></g></svg>',subscript:'<svg width="24" height="24"><path d="M10.4 10l4.6 4.6-1.4 1.4L9 11.4 4.4 16 3 14.6 7.6 10 3 5.4 4.4 4 9 8.6 13.6 4 15 5.4 10.4 10zM21 19h-5v-1l1-.8 1.7-1.6c.3-.4.5-.8.5-1.2 0-.3 0-.6-.2-.7-.2-.2-.5-.3-.9-.3a2 2 0 00-.8.2l-.7.3-.4-1.1 1-.6 1.2-.2c.8 0 1.4.3 1.8.7.4.4.6.9.6 1.5s-.2 1.1-.5 1.6a8 8 0 01-1.3 1.3l-.6.6h2.6V19z" fill-rule="nonzero"/></svg>',superscript:'<svg width="24" height="24"><path d="M15 9.4L10.4 14l4.6 4.6-1.4 1.4L9 15.4 4.4 20 3 18.6 7.6 14 3 9.4 4.4 8 9 12.6 13.6 8 15 9.4zm5.9 1.6h-5v-1l1-.8 1.7-1.6c.3-.5.5-.9.5-1.3 0-.3 0-.5-.2-.7-.2-.2-.5-.3-.9-.3l-.8.2-.7.4-.4-1.2c.2-.2.5-.4 1-.5.3-.2.8-.2 1.2-.2.8 0 1.4.2 1.8.6.4.4.6 1 .6 1.6 0 .5-.2 1-.5 1.5l-1.3 1.4-.6.5h2.6V11z" fill-rule="nonzero"/></svg>',"table-caption":'<svg width="24" height="24"><g fill-rule="nonzero"><rect width="12" height="2" x="3" y="4" rx="1"/><path d="M19 8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2v-8c0-1.1.9-2 2-2h14zM5 15v3h6v-3H5zm14 0h-6v3h6v-3zm0-5h-6v3h6v-3zM5 13h6v-3H5v3z"/></g></svg>',"table-cell-classes":'<svg width="24" height="24"><g fill-rule="evenodd"><path fill-rule="nonzero" d="M13 4v9H3V6c0-1.1.9-2 2-2h8zm-2 2H5v5h6V6z"/><path fill-rule="nonzero" d="M13 4h6a2 2 0 012 2v7h-8v-2h6V6h-6V4z" opacity=".2"/><path d="M18 20l-2.6 1.6.7-3-2.4-2 3.1-.2 1.2-2.9 1.2 2.9 3 .2-2.3 2 .7 3z"/><path fill-rule="nonzero" d="M3 13v5c0 1.1.9 2 2 2h8v-7h-2v5H5v-5H3z" opacity=".2"/></g></svg>',"table-cell-properties":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm-8 9H5v5h6v-5zm8 0h-6v5h6v-5zm-8-7H5v5h6V6z"/></svg>',"table-cell-select-all":'<svg width="24" height="24"><g fill-rule="evenodd"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm0 2H5v12h14V6z"/><path d="M13 6v5h6v2h-6v5h-2v-5H5v-2h6V6h2z" opacity=".2"/></g></svg>',"table-cell-select-inner":'<svg width="24" height="24"><g fill-rule="evenodd"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm0 2H5v12h14V6z" opacity=".2"/><path d="M13 6v5h6v2h-6v5h-2v-5H5v-2h6V6h2z"/></g></svg>',"table-classes":'<svg width="24" height="24"><g fill-rule="evenodd"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v7h-8v7H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm-8 9H5v5h6v-5zm8-7h-6v5h6V6zm-8 0H5v5h6V6z"/><path d="M18 20l-2.6 1.6.7-3-2.4-2 3.1-.2 1.2-2.9 1.2 2.9 3 .2-2.3 2 .7 3z"/></g></svg>',"table-delete-column":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm-4 4h-2V6h-2v2H9V6H5v12h4v-2h2v2h2v-2h2v2h4V6h-4v2zm.3.5l1 1.2-3 2.3 3 2.3-1 1.2L12 13l-3.3 2.6-1-1.2 3-2.3-3-2.3 1-1.2L12 11l3.3-2.5z"/></svg>',"table-delete-row":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm0 2H5v3h2.5v2H5v2h2.5v2H5v3h14v-3h-2.5v-2H19v-2h-2.5V9H19V6zm-4.7 1.8l1.2 1L13 12l2.6 3.3-1.2 1-2.3-3-2.3 3-1.2-1L11 12 8.5 8.7l1.2-1 2.3 3 2.3-3z"/></svg>',"table-delete-table":'<svg width="24" height="24"><g fill-rule="nonzero"><path d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zM5 6v12h14V6H5z"/><path d="M14.4 8.6l1 1-2.3 2.4 2.3 2.4-1 1-2.4-2.3-2.4 2.3-1-1 2.3-2.4-2.3-2.4 1-1 2.4 2.3z"/></g></svg>',"table-insert-column-after":'<svg width="24" height="24"><path fill-rule="nonzero" d="M20 4c.6 0 1 .4 1 1v2a1 1 0 01-2 0V6h-8v12h8v-1a1 1 0 012 0v2c0 .5-.4 1-.9 1H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h15zM9 13H5v5h4v-5zm7-5c.5 0 1 .4 1 .9V11h2a1 1 0 01.1 2H17v2a1 1 0 01-2 .1V13h-2a1 1 0 01-.1-2H15V9c0-.6.4-1 1-1zM9 6H5v5h4V6z"/></svg>',"table-insert-column-before":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H4a1 1 0 01-1-1v-2a1 1 0 012 0v1h8V6H5v1a1 1 0 11-2 0V5c0-.6.4-1 1-1h15zm0 9h-4v5h4v-5zM8 8c.5 0 1 .4 1 .9V11h2a1 1 0 01.1 2H9v2a1 1 0 01-2 .1V13H5a1 1 0 01-.1-2H7V9c0-.6.4-1 1-1zm11-2h-4v5h4V6z"/></svg>',"table-insert-row-above":'<svg width="24" height="24"><path fill-rule="nonzero" d="M6 4a1 1 0 110 2H5v6h14V6h-1a1 1 0 010-2h2c.6 0 1 .4 1 1v13a2 2 0 01-2 2H5a2 2 0 01-2-2V5c0-.6.4-1 1-1h2zm5 10H5v4h6v-4zm8 0h-6v4h6v-4zM12 3c.5 0 1 .4 1 .9V6h2a1 1 0 010 2h-2v2a1 1 0 01-2 .1V8H9a1 1 0 010-2h2V4c0-.6.4-1 1-1z"/></svg>',"table-insert-row-after":'<svg width="24" height="24"><path fill-rule="nonzero" d="M12 13c.5 0 1 .4 1 .9V16h2a1 1 0 01.1 2H13v2a1 1 0 01-2 .1V18H9a1 1 0 01-.1-2H11v-2c0-.6.4-1 1-1zm6 7a1 1 0 010-2h1v-6H5v6h1a1 1 0 010 2H4a1 1 0 01-1-1V6c0-1.1.9-2 2-2h14a2 2 0 012 2v13c0 .5-.4 1-.9 1H18zM11 6H5v4h6V6zm8 0h-6v4h6V6z"/></svg>',"table-left-header":'<svg width="24" height="24"><path d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm0 9h-4v5h4v-5zm-6 0H9v5h4v-5zm0-7H9v5h4V6zm6 0h-4v5h4V6z"/></svg>',"table-merge-cells":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zM5 15.5V18h3v-2.5H5zm14-5h-9V18h9v-7.5zM19 6h-4v2.5h4V6zM8 6H5v2.5h3V6zm5 0h-3v2.5h3V6zm-8 7.5h3v-3H5v3z"/></svg>',"table-row-numbering-rtl":'<svg width="24" height="24"><path d="M6 4a2 2 0 00-2 2v13c0 1.1.9 2 2 2h12a2 2 0 002-2V6a2 2 0 00-2-2H6zm0 12h8v3H6v-3zm11 0c.6 0 1 .4 1 1v1a1 1 0 01-2 0v-1c0-.6.4-1 1-1zM6 11h8v3H6v-3zm11 0c.6 0 1 .4 1 1v1a1 1 0 01-2 0v-1c0-.6.4-1 1-1zM6 6h8v3H6V6zm11 0c.6 0 1 .4 1 1v1a1 1 0 11-2 0V7c0-.6.4-1 1-1z"/></svg>',"table-row-numbering":'<svg width="24" height="24"><path d="M18 4a2 2 0 012 2v13a2 2 0 01-2 2H6a2 2 0 01-2-2V6c0-1.1.9-2 2-2h12zm0 12h-8v3h8v-3zM7 16a1 1 0 00-1 1v1a1 1 0 002 0v-1c0-.6-.4-1-1-1zm11-5h-8v3h8v-3zM7 11a1 1 0 00-1 1v1a1 1 0 002 0v-1c0-.6-.4-1-1-1zm11-5h-8v3h8V6zM7 6a1 1 0 00-1 1v1a1 1 0 102 0V7c0-.6-.4-1-1-1z"/></svg>',"table-row-properties":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zM5 15v3h6v-3H5zm14 0h-6v3h6v-3zm0-9h-6v3h6V6zM5 9h6V6H5v3z"/></svg>',"table-split-cells":'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zM8 15.5H5V18h3v-2.5zm11-5h-9V18h9v-7.5zm-2.5 1l1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2zm-8.5-1H5v3h3v-3zM19 6h-4v2.5h4V6zM8 6H5v2.5h3V6zm5 0h-3v2.5h3V6z"/></svg>',"table-top-header":'<svg width="24" height="24"><path d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zm-8 11H5v3h6v-3zm8 0h-6v3h6v-3zm0-5h-6v3h6v-3zM5 13h6v-3H5v3z"/></svg>',table:'<svg width="24" height="24"><path fill-rule="nonzero" d="M19 4a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6c0-1.1.9-2 2-2h14zM5 14v4h6v-4H5zm14 0h-6v4h6v-4zm0-6h-6v4h6V8zM5 12h6V8H5v4z"/></svg>',template:'<svg width="24" height="24"><path d="M19 19v-1H5v1h14zM9 16v-4a5 5 0 116 0v4h4a2 2 0 012 2v3H3v-3c0-1.1.9-2 2-2h4zm4 0v-5l.8-.6a3 3 0 10-3.6 0l.8.6v5h2z" fill-rule="nonzero"/></svg>',"temporary-placeholder":'<svg width="24" height="24"><g fill-rule="evenodd"><path d="M9 7.6V6h2.5V4.5a.5.5 0 111 0V6H15v1.6a8 8 0 11-6 0zm-2.6 5.3a.5.5 0 00.3.6c.3 0 .6 0 .6-.3l.1-.2a5 5 0 013.3-2.8c.3-.1.4-.4.4-.6-.1-.3-.4-.5-.6-.4a6 6 0 00-4.1 3.7z"/><circle cx="14" cy="4" r="1"/><circle cx="12" cy="2" r="1"/><circle cx="10" cy="4" r="1"/></g></svg>',"text-color":'<svg width="24" height="24"><g fill-rule="evenodd"><path id="tox-icon-text-color__color" d="M3 18h18v3H3z"/><path d="M8.7 16h-.8a.5.5 0 01-.5-.6l2.7-9c.1-.3.3-.4.5-.4h2.8c.2 0 .4.1.5.4l2.7 9a.5.5 0 01-.5.6h-.8a.5.5 0 01-.4-.4l-.7-2.2c0-.3-.3-.4-.5-.4h-3.4c-.2 0-.4.1-.5.4l-.7 2.2c0 .3-.2.4-.4.4zm2.6-7.6l-.6 2a.5.5 0 00.5.6h1.6a.5.5 0 00.5-.6l-.6-2c0-.3-.3-.4-.5-.4h-.4c-.2 0-.4.1-.5.4z"/></g></svg>',toc:'<svg width="24" height="24"><path d="M5 5c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 110-2zm3 0h11c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 110-2zm-3 8c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 010-2zm3 0h11c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 010-2zm0-4c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 110-2zm3 0h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm-3 8c.6 0 1 .4 1 1s-.4 1-1 1a1 1 0 010-2zm3 0h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2z" fill-rule="evenodd"/></svg>',translate:'<svg width="24" height="24"><path d="M12.7 14.3l-.3.7-.4.7-2.2-2.2-3.1 3c-.3.4-.8.4-1 0a.7.7 0 010-1l3.1-3A12.4 12.4 0 016.7 9H8a10.1 10.1 0 001.7 2.4c.5-.5 1-1.1 1.4-1.8l.9-2H4.7a.7.7 0 110-1.5h4.4v-.7c0-.4.3-.8.7-.8.4 0 .7.4.7.8v.7H15c.4 0 .8.3.8.7 0 .4-.4.8-.8.8h-1.4a12.3 12.3 0 01-1 2.4 13.5 13.5 0 01-1.7 2.3l1.9 1.8zm4.3-3l2.7 7.3a.5.5 0 01-.4.7 1 1 0 01-1-.7l-.6-1.5h-3.4l-.6 1.5a1 1 0 01-1 .7.5.5 0 01-.4-.7l2.7-7.4a1 1 0 012 0zm-2.2 4.4h2.4L16 12.5l-1.2 3.2z" fill-rule="evenodd"/></svg>',underline:'<svg width="24" height="24"><path d="M16 5c.6 0 1 .4 1 1v5.5a4 4 0 01-.4 1.8l-1 1.4a5.3 5.3 0 01-5.5 1 5 5 0 01-1.6-1c-.5-.4-.8-.9-1.1-1.4a4 4 0 01-.4-1.8V6c0-.6.4-1 1-1s1 .4 1 1v5.5c0 .3 0 .6.2 1l.6.7a3.3 3.3 0 002.2.8 3.4 3.4 0 002.2-.8c.3-.2.4-.5.6-.8l.2-.9V6c0-.6.4-1 1-1zM8 17h8c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 010-2z" fill-rule="evenodd"/></svg>',undo:'<svg width="24" height="24"><path d="M6.4 8H12c3.7 0 6.2 2 6.8 5.1.6 2.7-.4 5.6-2.3 6.8a1 1 0 01-1-1.8c1.1-.6 1.8-2.7 1.4-4.6-.5-2.1-2.1-3.5-4.9-3.5H6.4l3.3 3.3a1 1 0 11-1.4 1.4l-5-5a1 1 0 010-1.4l5-5a1 1 0 011.4 1.4L6.4 8z" fill-rule="nonzero"/></svg>',unlink:'<svg width="24" height="24"><path d="M6.2 12.3a1 1 0 011.4 1.4l-2 2a2 2 0 102.6 2.8l4.8-4.8a1 1 0 000-1.4 1 1 0 111.4-1.3 2.9 2.9 0 010 4L9.6 20a3.9 3.9 0 01-5.5-5.5l2-2zm11.6-.6a1 1 0 01-1.4-1.4l2.1-2a2 2 0 10-2.7-2.8L11 10.3a1 1 0 000 1.4A1 1 0 119.6 13a2.9 2.9 0 010-4L14.4 4a3.9 3.9 0 015.5 5.5l-2 2zM7.6 6.3a.8.8 0 01-1 1.1L3.3 4.2a.7.7 0 111-1l3.2 3.1zM5.1 8.6a.8.8 0 010 1.5H3a.8.8 0 010-1.5H5zm5-3.5a.8.8 0 01-1.5 0V3a.8.8 0 011.5 0V5zm6 11.8a.8.8 0 011-1l3.2 3.2a.8.8 0 01-1 1L16 17zm-2.2 2a.8.8 0 011.5 0V21a.8.8 0 01-1.5 0V19zm5-3.5a.7.7 0 110-1.5H21a.8.8 0 010 1.5H19z" fill-rule="nonzero"/></svg>',unlock:'<svg width="24" height="24"><path d="M16 5c.8 0 1.5.3 2.1.9.6.6.9 1.3.9 2.1v3h-2V8a1 1 0 00-.3-.7A1 1 0 0016 7h-2a1 1 0 00-.7.3 1 1 0 00-.3.7v3h.3c.2 0 .3 0 .5.2l.2.6v7.4c0 .3 0 .4-.2.6l-.6.2H4.8c-.3 0-.4 0-.6-.2a.7.7 0 01-.2-.6v-7.4c0-.3 0-.4.2-.6l.5-.2H11V8c0-.8.3-1.5.9-2.1.6-.6 1.3-.9 2.1-.9h2z" fill-rule="evenodd"/></svg>',"unordered-list":'<svg width="24" height="24"><path d="M11 5h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0 6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zm0 6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 010-2zM4.5 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1zm0 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1zm0 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1z" fill-rule="evenodd"/></svg>',unselected:'<svg width="24" height="24"><path fill-rule="nonzero" d="M6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6c0-1.1.9-2 2-2zm0 1a1 1 0 00-1 1v12c0 .6.4 1 1 1h12c.6 0 1-.4 1-1V6c0-.6-.4-1-1-1H6z"/></svg>',upload:'<svg width="24" height="24"><path d="M18 19v-2a1 1 0 012 0v3c0 .6-.4 1-1 1H5a1 1 0 01-1-1v-3a1 1 0 012 0v2h12zM11 6.4L8.7 8.7a1 1 0 01-1.4-1.4l4-4a1 1 0 011.4 0l4 4a1 1 0 11-1.4 1.4L13 6.4V16a1 1 0 01-2 0V6.4z" fill-rule="nonzero"/></svg>',user:'<svg width="24" height="24"><path d="M12 24a12 12 0 110-24 12 12 0 010 24zm-8.7-5.3a11 11 0 0017.4 0C19.4 16.3 14.6 15 12 15c-2.6 0-7.4 1.3-8.7 3.7zM12 13c2.2 0 4-2 4-4.5S14.2 4 12 4 8 6 8 8.5 9.8 13 12 13z" fill-rule="nonzero"/></svg>',"vertical-align":'<svg width="24" height="24"><g fill-rule="nonzero"><rect width="18" height="2" x="3" y="11" rx="1"/><path d="M12 2c.6 0 1 .4 1 1v4l2-1.3a1 1 0 011.2 1.5l-.1.1-4.1 3-4-3a1 1 0 011-1.7l2 1.5V3c0-.6.4-1 1-1zm0 11.8l4 2.9a1 1 0 01-1 1.7l-2-1.5V21c0 .5-.4 1-.9 1H12a1 1 0 01-1-1v-4l-2 1.3a1 1 0 01-1.2-.1l-.1-.1a1 1 0 01.1-1.3l.1-.1 4.1-3z"/></g></svg>',visualblocks:'<svg width="24" height="24"><path d="M9 19v2H7v-2h2zm-4 0v2a2 2 0 01-2-2h2zm8 0v2h-2v-2h2zm8 0a2 2 0 01-2 2v-2h2zm-4 0v2h-2v-2h2zM15 7a1 1 0 010 2v7a1 1 0 01-2 0V9h-1v7a1 1 0 01-2 0v-4a2.5 2.5 0 01-.2-5H15zM5 15v2H3v-2h2zm16 0v2h-2v-2h2zM5 11v2H3v-2h2zm16 0v2h-2v-2h2zM5 7v2H3V7h2zm16 0v2h-2V7h2zM5 3v2H3c0-1.1.9-2 2-2zm8 0v2h-2V3h2zm6 0a2 2 0 012 2h-2V3zM9 3v2H7V3h2zm8 0v2h-2V3h2z" fill-rule="evenodd"/></svg>',visualchars:'<svg width="24" height="24"><path d="M10 5h7a1 1 0 010 2h-1v11a1 1 0 01-2 0V7h-2v11a1 1 0 01-2 0v-6c-.5 0-1 0-1.4-.3A3.4 3.4 0 016.8 10a3.3 3.3 0 010-2.8 3.4 3.4 0 011.8-1.8L10 5z" fill-rule="evenodd"/></svg>',warning:'<svg width="24" height="24"><path d="M19.8 18.3c.2.5.3.9 0 1.2-.1.3-.5.5-1 .5H5.2c-.5 0-.9-.2-1-.5-.3-.3-.2-.7 0-1.2L11 4.7l.5-.5.5-.2c.2 0 .3 0 .5.2.2 0 .3.3.5.5l6.8 13.6zM12 18c.3 0 .5-.1.7-.3.2-.2.3-.4.3-.7a1 1 0 00-.3-.7 1 1 0 00-.7-.3 1 1 0 00-.7.3 1 1 0 00-.3.7c0 .3.1.5.3.7.2.2.4.3.7.3zm.7-3l.3-4a1 1 0 00-.3-.7 1 1 0 00-.7-.3 1 1 0 00-.7.3 1 1 0 00-.3.7l.3 4h1.4z" fill-rule="evenodd"/></svg>',"zoom-in":'<svg width="24" height="24"><path d="M16 17.3a8 8 0 111.4-1.4l4.3 4.4a1 1 0 01-1.4 1.4l-4.4-4.3zm-5-.3a6 6 0 100-12 6 6 0 000 12zm-1-9a1 1 0 012 0v6a1 1 0 01-2 0V8zm-2 4a1 1 0 010-2h6a1 1 0 010 2H8z" fill-rule="nonzero"/></svg>',"zoom-out":'<svg width="24" height="24"><path d="M16 17.3a8 8 0 111.4-1.4l4.3 4.4a1 1 0 01-1.4 1.4l-4.4-4.3zm-5-.3a6 6 0 100-12 6 6 0 000 12zm-3-5a1 1 0 010-2h6a1 1 0 010 2H8z" fill-rule="nonzero"/></svg>'}}); \ No newline at end of file diff --git a/apps/st/public/tinymce/jquery.tinymce.min.js b/apps/st/public/tinymce/jquery.tinymce.min.js new file mode 100644 index 000000000..46e24d268 --- /dev/null +++ b/apps/st/public/tinymce/jquery.tinymce.min.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +/** + * Jquery integration plugin. + * + * @class tinymce.core.JqueryIntegration + * @private + */ +!function(){function f(){ +// Reference to tinymce needs to be lazily evaluated since tinymce +// might be loaded through the compressor or other means +return d.tinymce}var p,c,u,s=[],d="undefined"!=typeof global?global:window,m=d.jQuery;m.fn.tinymce=function(o){var e,t,i,n,l=this,r=""; +// No match then just ignore the call +return l.length? +// Get editor instance +o?(l.css("visibility","hidden"), +// Load TinyMCE on demand, if we need to +d.tinymce||c||!(e=o.script_url)? +// Delay the init call until tinymce is loaded +1===c?s.push(a):a():(c=1,t=e.substring(0,e.lastIndexOf("/")), +// Check if it's a dev/src version they want to load then +// make sure that all plugins, themes etc are loaded in source mode as well +-1!=e.indexOf(".min")&&(r=".min"), +// Setup tinyMCEPreInit object this will later be used by the TinyMCE +// core script to locate other resources like CSS files, dialogs etc +// You can also predefined a tinyMCEPreInit object and then it will use that instead +d.tinymce=d.tinyMCEPreInit||{base:t,suffix:r}, +// url contains gzip then we assume it's a compressor +-1!=e.indexOf("gzip")&&(i=o.language||"en",e=e+(/\?/.test(e)?"&":"?")+"js=true&core=true&suffix="+escape(r)+"&themes="+escape(o.theme||"modern")+"&plugins="+escape(o.plugins||"")+"&languages="+(i||""), +// Check if compressor script is already loaded otherwise setup a basic one +d.tinyMCE_GZ||(d.tinyMCE_GZ={start:function(){function n(e){f().ScriptLoader.markDone(f().baseURI.toAbsolute(e))} +// Add core languages +n("langs/"+i+".js"), +// Add themes with languages +n("themes/"+o.theme+"/theme"+r+".js"),n("themes/"+o.theme+"/langs/"+i+".js"), +// Add plugins with languages +m.each(o.plugins.split(","),function(e,t){t&&(n("plugins/"+t+"/plugin"+r+".js"),n("plugins/"+t+"/langs/"+i+".js"))})},end:function(){}})),(n=document.createElement("script")).type="text/javascript",n.onload=n.onreadystatechange=function(e){e=e||window.event,2===c||"load"!=e.type&&!/complete|loaded/.test(n.readyState)||(f().dom.Event.domLoaded=1,c=2, +// Execute callback after mainscript has been loaded and before the initialization occurs +o.script_loaded&&o.script_loaded(),a(),m.each(s,function(e,t){t()}))},n.src=e,document.body.appendChild(n)),l):f()?f().get(l[0].id):null:l;function a(){var a=[],c=0; +// Apply patches to the jQuery object, only once +u||(v(),u=!0), +// Create an editor instance for each matched node +l.each(function(e,t){var n,i=t.id,r=o.oninit; +// Generate unique id for target element if needed +i||(t.id=i=f().DOM.uniqueId()), +// Only init the editor once +f().get(i)||( +// Create editor instance and render it +n=f().createEditor(i,o),a.push(n),n.on("init",function(){var e,t=r;l.css("visibility",""), +// Run this if the oninit setting is defined +// this logic will fire the oninit callback ones each +// matched editor instance is initialized +r&&++c==a.length&&("string"==typeof t&&(e=-1===t.indexOf(".")?null:f().resolve(t.replace(/\.\w+$/,"")),t=f().resolve(t)), +// Call the oninit function with the object +t.apply(e||f(),a))}))}), +// Render the editor instances in a separate loop since we +// need to have the full editors array used in the onInit calls +m.each(a,function(e,t){t.render()})}}, +// Add :tinymce pseudo selector this will select elements that has been converted into editor instances +// it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements. +m.extend(m.expr[":"],{tinymce:function(e){var t;return!!(e.id&&"tinymce"in d&&(t=f().get(e.id))&&t.editorManager===f())}}); +// This function patches internal jQuery functions so that if +// you for example remove an div element containing an editor it's +// automatically destroyed by the TinyMCE API +var v=function(){function r(e){ +// If the function is remove +"remove"===e&&this.each(function(e,t){var n=u(t);n&&n.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(e,t){var n=f().get(t.id.replace(/_parent$/,""));n&&n.remove()})}function o(i){var e,t=this; +// Handle set value +/*jshint eqnull:true */if(null!=i)r.call(t), +// Saves the contents before get/set value of textarea/div +t.each(function(e,t){var n;(n=f().get(t.id))&&n.setContent(i)});else if(0<t.length&&(e=f().get(t[0].id)))return e.getContent()}function l(e){return e&&e.length&&d.tinymce&&e.is(":tinymce")} +// Removes any child editor instances by looking for editor wrapper elements +var u=function(e){var t=null;return e&&e.id&&d.tinymce?f().get(e.id):t},s={}; +// Loads or saves contents from/to textarea if the value +// argument is defined it will set the TinyMCE internal contents +// Patch some setter/getter functions these will +// now be able to set/get the contents of editor instances for +// example $('#editorid').html('Content'); will update the TinyMCE iframe instance +m.each(["text","html","val"],function(e,t){var a=s[t]=m.fn[t],c="text"===t;m.fn[t]=function(e){var t=this;if(!l(t))return a.apply(t,arguments);if(e!==p)return o.call(t.filter(":tinymce"),e),a.apply(t.not(":tinymce"),arguments),t;// return original set for chaining +var i="",r=arguments;return(c?t:t.eq(0)).each(function(e,t){var n=u(t);i+=n?c?n.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):n.getContent({save:!0}):a.apply(m(t),r)}),i}}), +// Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe +m.each(["append","prepend"],function(e,t){var n=s[t]=m.fn[t],r="prepend"===t;m.fn[t]=function(i){var e=this;return l(e)?i!==p?("string"==typeof i&&e.filter(":tinymce").each(function(e,t){var n=u(t);n&&n.setContent(r?i+n.getContent():n.getContent()+i)}),n.apply(e.not(":tinymce"),arguments),e):void 0:n.apply(e,arguments)}}), +// Makes sure that the editor instance gets properly destroyed when the parent element is removed +m.each(["remove","replaceWith","replaceAll","empty"],function(e,t){var n=s[t]=m.fn[t];m.fn[t]=function(){return r.call(this,t),n.apply(this,arguments)}}),s.attr=m.fn.attr, +// Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents +m.fn.attr=function(e,t){var n=this,i=arguments;if(!e||"value"!==e||!l(n))return s.attr.apply(n,i);if(t!==p)return o.call(n.filter(":tinymce"),t),s.attr.apply(n.not(":tinymce"),i),n;// return original set for chaining +var r=n[0],a=u(r);return a?a.getContent({save:!0}):s.attr.apply(m(r),i)}}}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/langs/README.md b/apps/st/public/tinymce/langs/README.md new file mode 100644 index 000000000..a52bf03f9 --- /dev/null +++ b/apps/st/public/tinymce/langs/README.md @@ -0,0 +1,3 @@ +This is where language files should be placed. + +Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/ diff --git a/apps/st/public/tinymce/langs/ko_KR.js b/apps/st/public/tinymce/langs/ko_KR.js new file mode 100644 index 000000000..be0c2156f --- /dev/null +++ b/apps/st/public/tinymce/langs/ko_KR.js @@ -0,0 +1 @@ +tinymce.addI18n("ko_KR",{"#":"#","Accessibility":"\uc811\uadfc\uc131","Accordion":"\uc544\ucf54\ub514\uc5b8","Accordion body...":"\uc544\ucf54\ub514\uc5b8 \ubcf8\ubb38...","Accordion summary...":"\uc544\ucf54\ub514\uc5b8 \uc694\uc57d...","Action":"\uc791\uc5c5","Activity":"\ud65c\ub3d9","Address":"\uc8fc\uc18c","Advanced":"\uc0c1\uc138","Align":"\uc815\ub82c","Align center":"\uc911\uc559 \uc815\ub82c","Align left":"\uc67c\ucabd \uc815\ub82c","Align right":"\uc624\ub978\ucabd \uc815\ub82c","Alignment":"\uc815\ub82c","Alignment {0}":"\uc815\ub82c {0}","All":"\ubaa8\ub450","Alternative description":"\ub300\uccb4 \uc124\uba85\ubb38","Alternative source":"\ub300\uccb4 \uc18c\uc2a4","Alternative source URL":"\ub300\uccb4 \uc18c\uc2a4 URL","Anchor":"\ub9c1\ud06c \uc9c0\uc810","Anchor...":"\uc575\ucee4...","Anchors":"\uc575\ucee4","Animals and Nature":"\ub3d9\ubb3c\uacfc \uc790\uc5f0","Arrows":"\ud654\uc0b4\ud45c","B":"\ud30c\ub791","Background color":"\ubc30\uacbd \uc0c9","Background color {0}":"\ubc30\uacbd \uc0c9\uc0c1 {0}","Black":"\uac80\uc740\uc0c9","Block":"\ube14\ub85d","Block {0}":"\ube14\ub85d {0}","Blockquote":"\uc778\uc6a9","Blocks":"\ube14\ub85d","Blue":"\ud30c\ub780\uc0c9","Blue component":"\uccad\uc0c9 \uc694\uc18c","Body":"\ubcf8\ubb38","Bold":"\uad75\uac8c","Border":"\ud14c\ub450\ub9ac","Border color":"\ud14c\ub450\ub9ac \uc0c9","Border style":"\ud14c\ub450\ub9ac \uc2a4\ud0c0\uc77c","Border width":"\ud14c\ub450\ub9ac \ub450\uaed8","Bottom":"\uc544\ub798 \ub9de\ucda4","Browse files":"\ud30c\uc77c \ud0d0\uc0c9","Browse for an image":"\uc774\ubbf8\uc9c0 \ucc3e\uae30","Browse links":"\ub9c1\ud06c \ud0d0\uc0c9","Bullet list":"\uae00\uba38\ub9ac \uae30\ud638 \ubaa9\ub85d","Cancel":"\ucde8\uc18c","Caption":"\ucea1\uc158","Cell":"\uc140","Cell padding":"\uc140 \uc548\ucabd \uc5ec\ubc31","Cell properties":"\uc140 \uc18d\uc131","Cell spacing":"\uc140 \uac04\uaca9","Cell styles":"\uc140 \ubaa8\uc591","Cell type":"\uc140 \uc720\ud615","Center":"\uac00\uc6b4\ub370 \ub9de\ucda4","Characters":"\ubb38\uc790 \uc218","Characters (no spaces)":"\ubb38\uc790 \uc218 (\uacf5\ubc31 \uc5c6\uc74c)","Circle":"\ub3d9\uadf8\ub77c\ubbf8","Class":"Class","Clear formatting":"\uc11c\uc2dd \uc9c0\uc6b0\uae30","Close":"\ub2eb\uae30","Code":"Code","Code sample...":"\ucf54\ub4dc \uc0d8\ud50c...","Code view":"\ucf54\ub4dc \ud45c\uc2dc","Color Picker":"\uc0c9 \uc120\ud0dd\uae30","Color swatch":"\uc0c9\uc0c1 \uacac\ubcf8","Cols":"\uc5f4 \uc218","Column":"\uc5f4","Column clipboard actions":"\uc5f4 \ud074\ub9bd\ubcf4\ub4dc \ub3d9\uc791","Column group":"\uc5f4 \uadf8\ub8f9","Column header":"\uc5f4 \uc81c\ubaa9","Constrain proportions":"\ube44\uc728 \uace0\uc815","Copy":"\ubcf5\uc0ac","Copy column":"\uc5f4 \ubcf5\uc0ac","Copy row":"\ud589 \ubcf5\uc0ac","Could not find the specified string.":"\uc9c0\uc815\ud55c \ubb38\uc790\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.","Could not load emojis":"\uc774\ubaa8\uc9c0\ub97c \ubd88\ub7ec\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4","Count":"\uac1c\uc218","Currency":"\ud1b5\ud654","Current window":"\ud604\uc7ac \ucc3d","Custom color":"\uc0ac\uc6a9\uc790 \uc9c0\uc815 \uc0c9","Custom...":"\uc0ac\uc6a9\uc790 \uc9c0\uc815...","Cut":"\uc798\ub77c\ub0b4\uae30","Cut column":"\uc5f4 \uc798\ub77c\ub0b4\uae30","Cut row":"\ud589 \uc798\ub77c\ub0b4\uae30","Dark Blue":"\uc9c4\ud55c \ud30c\ub780\uc0c9","Dark Gray":"\uc9c4\ud55c \ud68c\uc0c9","Dark Green":"\uc9c4\ud55c \ucd08\ub85d\uc0c9","Dark Orange":"\uc9c4\ud55c \uc8fc\ud669\uc0c9","Dark Purple":"\uc9c4\ud55c \ubcf4\ub77c\uc0c9","Dark Red":"\uc9c4\ud55c \ube68\uac04\uc0c9","Dark Turquoise":"\uc9c4\ud55c \uccad\ub85d\uc0c9","Dark Yellow":"\uc9c4\ud55c \ub178\ub780\uc0c9","Dashed":"\ud30c\uc120","Date/time":"\ub0a0\uc9dc/\uc2dc\uac04","Decrease indent":"\ub0b4\uc5b4\uc4f0\uae30","Default":"\uae30\ubcf8\uac12","Delete accordion":"\uc544\ucf54\ub514\uc5b8 \uc0ad\uc81c","Delete column":"\uc5f4 \uc0ad\uc81c","Delete row":"\ud589 \uc0ad\uc81c","Delete table":"\ud45c \uc0ad\uc81c","Dimensions":"\ud06c\uae30","Disc":"\ub514\uc2a4\ud06c","Div":"Div","Document":"\ubb38\uc11c","Dotted":"\uc810\uc120","Double":"\uc774\uc911 \uc2e4\uc120","Drop an image here":"\uc5ec\uae30\ub85c \uc774\ubbf8\uc9c0\ub97c \ub04c\uc5b4\uc624\uc138\uc694","Dropped file type is not supported":"\ub04c\uc5b4\ub2e4 \ub193\uc740 \ud30c\uc77c \ud615\uc2dd\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4","Edit":"\ud3b8\uc9d1","Embed":"\uc0bd\uc785","Emojis":"\uc774\ubaa8\uc9c0","Emojis...":"\uc774\ubaa8\uc9c0...","Error":"\uc624\ub958","Error: Form submit field collision.":"\uc624\ub958: \uc591\uc2dd \uc81c\ucd9c \ud544\ub4dc \ubd88\uc77c\uce58","Error: No form element found.":"\uc624\ub958: \uc591\uc2dd \ud56d\ubaa9 \uc5c6\uc74c","Extended Latin":"\ud655\uc7a5 \ub77c\ud2f4\uc5b4","Failed to initialize plugin: {0}":"\ud50c\ub7ec\uadf8\uc778 {0}\uc758 \ucd08\uae30\ud654\uac00 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4","Failed to load plugin url: {0}":"\ud50c\ub7ec\uadf8\uc778 URL {0}\uc744 \ubd88\ub7ec\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.","Failed to load plugin: {0} from url {1}":"URL {1}\ub85c\ubd80\ud130 \ud50c\ub7ec\uadf8\uc778 {0}\uc744 \ubd88\ub7ec\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.","Failed to upload image: {0}":"\uc774\ubbf8\uc9c0{0}\uc744(\ub97c) \uc5c5\ub85c\ub4dc \ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.","File":"\ud30c\uc77c","Find":"\ucc3e\uae30","Find (if searchreplace plugin activated)":"\ucc3e\uae30 (searchreplace \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uacbd\uc6b0)","Find and Replace":"\ucc3e\uae30 \ubc0f \ubc14\uafb8\uae30","Find and replace...":"\ucc3e\uae30 \ubc0f \ubc14\uafb8\uae30...","Find in selection":"\uc120\ud0dd\ub41c \ubd80\ubd84\uc5d0\uc11c \uac80\uc0c9","Find whole words only":"\ubaa8\ub450 \uc77c\uce58\ud558\ub294 \ub2e8\uc5b4 \ucc3e\uae30","Flags":"\uae43\ubc1c","Focus to contextual toolbar":"\ucee8\ud14d\uc2a4\ud2b8 \ud234\ubc14\uc5d0 \uac15\uc870\ud45c\uc2dc","Focus to element path":"\uc694\uc18c \uacbd\ub85c\uc5d0 \uac15\uc870\ud45c\uc2dc","Focus to menubar":"\uba54\ub274\ubc14\uc5d0 \uac15\uc870\ud45c\uc2dc","Focus to toolbar":"\ud234\ubc14\uc5d0 \uac15\uc870\ud45c\uc2dc","Font":"\uae00\uaf34","Font size {0}":"\ud3f0\ud2b8 \ud06c\uae30 {0}","Font sizes":"\uae00\uaf34 \ud06c\uae30","Font {0}":"\ud3f0\ud2b8 {0}","Fonts":"\uae00\uaf34","Food and Drink":"\uc74c\uc2dd\uacfc \uc74c\ub8cc","Footer":"\ud478\ud130","Format":"\uc11c\uc2dd","Format {0}":"\ud3ec\ub9f7 {0}","Formats":"\uc11c\uc2dd","Fullscreen":"\uc804\uccb4 \ud654\uba74","G":"\ub179\uc0c9","General":"\uc77c\ubc18","Gray":"\ud68c\uc0c9","Green":"\ucd08\ub85d\uc0c9","Green component":"\ub179\uc0c9 \uc694\uc18c","Groove":"\uc785\uccb4 \ud14c\ub450\ub9ac","Handy Shortcuts":"\uc720\uc6a9\ud55c \ub2e8\ucd95\ud0a4","Header":"\uc81c\ubaa9","Header cell":"\ud5e4\ub354 \uc140","Heading 1":"\uc81c\ubaa9 1","Heading 2":"\uc81c\ubaa9 2","Heading 3":"\uc81c\ubaa9 3","Heading 4":"\uc81c\ubaa9 4","Heading 5":"\uc81c\ubaa9 5","Heading 6":"\uc81c\ubaa9 6","Headings":"\uc81c\ubaa9","Height":"\ub192\uc774","Help":"\ub3c4\uc6c0\ub9d0","Hex color code":"16\uc9c4\uc218 \uc0c9\uc0c1 \ucf54\ub4dc","Hidden":"\uc228\uae40","Horizontal align":"\uc218\ud3c9 \uc815\ub82c","Horizontal line":"\uad6c\ubd84\uc120","Horizontal space":"\uc88c\uc6b0 \uc5ec\ubc31","ID":"ID","ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.":"ID\ub294 \ubb38\uc790\ub85c \uc2dc\uc791\ud574\uc57c \ud558\uba70, \uadf8 \ub2e4\uc74c\uc5d0\ub294 \ubb38\uc790, \uc22b\uc790, \ub300\uc2dc, \uad6c\ub450\uc810, \ucf5c\ub860, \ubc11\uc904 \ubb38\uc790\uac00 \uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4.","Image is decorative":"\uc774\ubbf8\uc9c0 \uc7a5\uc2dd \uac00\ub2a5","Image list":"\uc774\ubbf8\uc9c0 \ubaa9\ub85d","Image title":"\uc774\ubbf8\uc9c0 \uc81c\ubaa9","Image...":"\uc774\ubbf8\uc9c0...","ImageProxy HTTP error: Could not find Image Proxy":"ImageProxy HTTP \uc624\ub958: \uc774\ubbf8\uc9c0 \ud504\ub85d\uc2dc\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4","ImageProxy HTTP error: Incorrect Image Proxy URL":"ImageProxy HTTP \uc624\ub958: \uc62c\ubc14\ub974\uc9c0 \uc54a\uc740 \uc774\ubbf8\uc9c0 \ud504\ub85d\uc2dc URL \uc8fc\uc18c","ImageProxy HTTP error: Rejected request":"ImageProxy HTTP \uc624\ub958: \uc694\uccad \uac70\ubd80","ImageProxy HTTP error: Unknown ImageProxy error":"ImageProxy HTTP \uc624\ub958: \uc54c \uc218 \uc5c6\ub294 \uc774\ubbf8\uc9c0 \ud504\ub85d\uc2dc \uc624\ub958","Increase indent":"\ub4e4\uc5ec\uc4f0\uae30","Inline":"\uc778\ub77c\uc778","Insert":"\uc0bd\uc785","Insert Template":"\ud15c\ud50c\ub9bf \uc0bd\uc785","Insert accordion":"\uc544\ucf54\ub514\uc5b8 \uc0bd\uc785","Insert column after":"\ub2e4\uc74c\uc5d0 \uc5f4 \uc0bd\uc785","Insert column before":"\uc774\uc804\uc5d0 \uc5f4 \uc0bd\uc785","Insert date/time":"\ub0a0\uc9dc/\uc2dc\uac04 \uc0bd\uc785","Insert image":"\uc774\ubbf8\uc9c0 \uc0bd\uc785","Insert link (if link plugin activated)":"\ub9c1\ud06c \uc0bd\uc785 (link \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uacbd\uc6b0)","Insert row after":"\ub2e4\uc74c\uc5d0 \ud589 \uc0bd\uc785","Insert row before":"\uc774\uc804\uc5d0 \ud589 \uc0bd\uc785","Insert table":"\ud45c \uc0bd\uc785","Insert template...":"\ud15c\ud50c\ub9bf \uc0bd\uc785...","Insert video":"\ube44\ub514\uc624 \uc0bd\uc785","Insert/Edit code sample":"\ucf54\ub4dc \uc0d8\ud50c \uc0bd\uc785/\ud3b8\uc9d1","Insert/edit image":"\uc774\ubbf8\uc9c0 \uc0bd\uc785/\ud3b8\uc9d1","Insert/edit link":"\ub9c1\ud06c \uc0bd\uc785/\ud3b8\uc9d1","Insert/edit media":"\ubbf8\ub514\uc5b4 \uc0bd\uc785/\ud3b8\uc9d1","Insert/edit video":"\ube44\ub514\uc624 \uc0bd\uc785/\ud3b8\uc9d1","Inset":"\uc140 \ud568\ubab0","Invalid hex color code: {0}":"\ubd80\uc801\uc808\ud55c 16\uc9c4\uc218 \uc0c9\uc0c1 \ucf54\ub4dc: {0}","Invalid input":"\ubd80\uc801\uc808\ud55c \uc785\ub825","Italic":"\uae30\uc6b8\uc784\uaf34","Justify":"\uc591\ucabd \uc815\ub82c","Keyboard Navigation":"\ub2e8\ucd95\ud0a4","Language":"\uc5b8\uc5b4","Learn more...":"\uc880 \ub354 \uc0b4\ud3b4\ubcf4\uae30...","Left":"\uc67c\ucabd \ub9de\ucda4","Left to right":"\uc67c\ucabd\uc5d0\uc11c \uc624\ub978\ucabd","Light Blue":"\ubc1d\uc740 \ud30c\ub780\uc0c9","Light Gray":"\ubc1d\uc740 \ud68c\uc0c9","Light Green":"\ubc1d\uc740 \ub179\uc0c9","Light Purple":"\ubc1d\uc740 \ubcf4\ub77c\uc0c9","Light Red":"\ubc1d\uc740 \ube68\uac04\uc0c9","Light Yellow":"\ubc1d\uc740 \ub178\ub780\uc0c9","Line height":"\uc904 \uac04\uaca9","Link list":"\ub9c1\ud06c \ubaa9\ub85d","Link...":"\ub9c1\ud06c...","List Properties":"\ud56d\ubaa9 \uc18d\uc131","List properties...":"\ud56d\ubaa9 \uc18d\uc131...","Loading emojis...":"\uc774\ubaa8\uc9c0 \ubd88\ub7ec\uc624\ub294 \uc911...","Loading...":"\ubd88\ub7ec\uc624\ub294 \uc911...","Lower Alpha":"\uc54c\ud30c\ubcb3 \uc18c\ubb38\uc790","Lower Greek":"\uadf8\ub9ac\uc2a4\uc5b4 \uc18c\ubb38\uc790","Lower Roman":"\ub85c\ub9c8\uc790 \uc18c\ubb38\uc790","Match case":"\ub300/\uc18c\ubb38\uc790 \uad6c\ubd84","Mathematical":"\uc218\ud559\uae30\ud638","Media poster (Image URL)":"\ubbf8\ub514\uc5b4 \ud3ec\uc2a4\ud130 (\uc774\ubbf8\uc9c0 URL)","Media...":"\ubbf8\ub514\uc5b4...","Medium Blue":"\uc911\uac04 \ud30c\ub780\uc0c9","Medium Gray":"\uc911\uac04 \ud68c\uc0c9","Medium Purple":"\uc911\uac04 \ubcf4\ub77c\uc0c9","Merge cells":"\uc140 \ubcd1\ud569","Middle":"\uac00\uc6b4\ub370 \ub9de\ucda4","Midnight Blue":"\uc9c4\ud55c \ud30c\ub780\uc0c9","More...":"\ub354 \ubcf4\uae30...","Name":"\uc774\ub984","Navy Blue":"\ub0a8\uc0c9","New document":"\uc0c8 \ubb38\uc11c","New window":"\uc0c8 \ucc3d","Next":"\ub2e4\uc74c","No":"\uc544\ub2c8\uc624","No alignment":"\uc815\ub82c \uc5c6\uc74c","No color":"\uc0c9 \uc5c6\uc74c","Nonbreaking space":"\ub744\uc5b4\uc4f0\uae30","None":"\uc5c6\uc74c","Numbered list":"\ubc88\ud638 \ub9e4\uae30\uae30 \ubaa9\ub85d","OR":"\ub610\ub294","Objects":"\ubb3c\uac74","Ok":"\ud655\uc778","Open help dialog":"\ub3c4\uc6c0\ub9d0 \ub2e4\uc774\uc5bc\ub85c\uadf8 \uc5f4\uae30","Open link":"\ub9c1\ud06c \uc5f4\uae30","Open link in...":"...\uc5d0\uc11c \ub9c1\ud06c \uc5f4\uae30","Open popup menu for split buttons":"\ubd84\ud560 \ubc84\ud2bc\uc73c\ub85c \ud31d\uc5c5 \uba54\ub274 \uc5f4\uae30","Orange":"\uc8fc\ud669\uc0c9","Outset":"\uc140 \ub3cc\ucd9c","Page break":"\ud398\uc774\uc9c0 \uad6c\ubd84\uc790","Paragraph":"\ub2e8\ub77d","Paste":"\ubd99\uc5ec\ub123\uae30","Paste as text":"\ud14d\uc2a4\ud2b8\ub85c \ubd99\uc5ec\ub123\uae30","Paste column after":"\ub2e4\uc74c\uc5d0 \uc5f4 \ubd99\uc5ec\ub123\uae30","Paste column before":"\uc774\uc804\uc5d0 \uc5f4 \ubd99\uc5ec\ub123\uae30","Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.":"\ud604\uc7ac \uc77c\ubc18 \ud14d\uc2a4\ud2b8 \ubaa8\ub4dc\uc5d0\uc11c \ubd99\uc5ec\ub123\uace0 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \uc635\uc158\uc744 \ub04c \ub54c\uae4c\uc9c0 \uc77c\ubc18 \ud14d\uc2a4\ud2b8\ub85c \ubd99\uc5ec\ub123\uc2b5\ub2c8\ub2e4.","Paste or type a link":"\ub9c1\ud06c\ub97c \ubd99\uc5ec\ub123\uac70\ub098 \uc785\ub825\ud558\uc2ed\uc2dc\uc624.","Paste row after":"\ub2e4\uc74c\uc5d0 \ud589 \ubd99\uc5ec\ub123\uae30","Paste row before":"\uc774\uc804\uc5d0 \ud589 \ubd99\uc5ec\ub123\uae30","Paste your embed code below:":"\uc0bd\uc785\ud560 \ucf54\ub4dc\ub97c \uc544\ub798\uc5d0 \ubd99\uc5ec \ub123\uc5b4\uc8fc\uc138\uc694.","People":"\uc0ac\ub78c","Plugins":"\ud50c\ub7ec\uadf8\uc778","Plugins installed ({0}):":"\uc124\uce58\ub41c \ud50c\ub7ec\uadf8\uc778({0}):","Powered by {0}":"{0}\uc5d0\uc11c \uc9c0\uc6d0","Pre":"Pre","Preferences":"\ud658\uacbd\uc124\uc815","Preformatted":"\uc0ac\uc804 \uc11c\uc2dd","Premium plugins:":"\ud504\ub9ac\ubbf8\uc5c4 \ud50c\ub7ec\uadf8\uc778:","Press the Up and Down arrow keys to resize the editor.":"\uc704\uc544\ub798 \ud654\uc0b4\ud45c \ud0a4\ub97c \ub20c\ub7ec \ud3b8\uc9d1\uae30 \ud06c\uae30\ub97c \uc870\uc815\ud558\uc138\uc694.","Press the arrow keys to resize the editor.":"\ud654\uc0b4\ud45c \ud0a4\ub97c \ub20c\ub7ec \ud3b8\uc9d1\uae30 \ud06c\uae30\ub97c \uc870\uc815\ud558\uc138\uc694.","Press {0} for help":"\ub3c4\uc6c0\ub9d0\uc744 \ubcf4\ub824\uba74 {0}\ub97c \ub204\ub974\uc138\uc694","Preview":"\ubbf8\ub9ac \ubcf4\uae30","Previous":"\uc774\uc804","Print":"\uc778\uc1c4","Print...":"\uc778\uc1c4...","Purple":"\ubcf4\ub77c\uc0c9","Quotations":"\uc778\uc6a9\ubb38","R":"\ube68\uac15","Range 0 to 255":"0\ubd80\ud130 255\uae4c\uc9c0\uc758 \ubc94\uc704","Red":"\ube68\uac04\uc0c9","Red component":"\uc801\uc0c9 \uc694\uc18c","Redo":"\ub2e4\uc2dc \uc2e4\ud589","Remove":"\uc81c\uac70","Remove color":"\uc0c9 \uc81c\uac70","Remove link":"\ub9c1\ud06c \uc81c\uac70","Replace":"\ubc14\uafb8\uae30","Replace all":"\ubaa8\ub450 \ubc14\uafb8\uae30","Replace with":"\ub2e4\uc74c\uc73c\ub85c \ubc14\uafb8\uae30:","Resize":"\ud06c\uae30 \uc870\uc808","Restore last draft":"\ub9c8\uc9c0\ub9c9 \ucd08\uc548 \ubcf5\uc6d0","Reveal or hide additional toolbar items":"\ud234\ubc14 \ud56d\ubaa9\uc744 \ucd94\uac00\ub85c \ud45c\uc2dc\ud558\uac70\ub098 \uc228\uae41\ub2c8\ub2e4","Rich Text Area":"\uc11c\uc2dd \ud14d\uc2a4\ud2b8 \uc601\uc5ed","Rich Text Area. Press ALT-0 for help.":"\uc11c\uc2dd \uc788\ub294 \ud14d\uc2a4\ud2b8 \uc601\uc5ed. ALT-0\uc744 \ub204\ub974\uba74 \ub3c4\uc6c0\ub9d0\uc744 \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.","Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help":"\uc11c\uc2dd \uc788\ub294 \ud14d\uc2a4\ud2b8 \uc601\uc5ed. ALT-F9\ub97c \ub204\ub974\uba74 \uba54\ub274, ALT-F10\uc744 \ub204\ub974\uba74 \ud234\ubc14, ALT-0\uc744 \ub204\ub974\uba74 \ub3c4\uc6c0\ub9d0\uc744 \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.","Ridge":"\ub3cc\ucd9c \ud14c\ub450\ub9ac","Right":"\uc624\ub978\ucabd \ub9de\ucda4","Right to left":"\uc624\ub978\ucabd\uc5d0\uc11c \uc67c\ucabd","Row":"\ud589","Row clipboard actions":"\ud589 \ud074\ub9bd\ubcf4\ub4dc \ub3d9\uc791","Row group":"\ud589 \uadf8\ub8f9","Row header":"\ud589 \uc81c\ubaa9","Row properties":"\ud589 \uc18d\uc131","Row type":"\ud589 \uc720\ud615","Rows":"\ud589 \uc218","Save":"\uc800\uc7a5","Save (if save plugin activated)":"\uc800\uc7a5 (save \ud50c\ub7ec\uadf8\uc778\uc774 \ud65c\uc131\ud654\ub41c \uacbd\uc6b0)","Scope":"\ubc94\uc704","Search":"\uac80\uc0c9","Select all":"\uc804\uccb4 \uc120\ud0dd","Select...":"\uc120\ud0dd...","Selection":"\uc120\ud0dd","Shortcut":"\ubc14\ub85c\uac00\uae30","Show blocks":"\ube14\ub85d \ud45c\uc2dc","Show caption":"\ucea1\uc158 \ud45c\uc2dc","Show invisible characters":"\ube44\ud45c\uc2dc \ubb38\uc790 \ud45c\uc2dc","Size":"\ud06c\uae30","Solid":"\uc2e4\uc120","Source":"\uc18c\uc2a4","Source code":"\uc18c\uc2a4\ucf54\ub4dc","Special Character":"\ud2b9\uc218 \ubb38\uc790","Special character...":"\ud2b9\uc218 \ubb38\uc790...","Split cell":"\uc140 \ubd84\ud560","Square":"\ub124\ubaa8","Start list at number":"\ubc88\ud638 \ub9ac\uc2a4\ud2b8 \uc2dc\uc791","Strikethrough":"\ucde8\uc18c\uc120","Style":"\uc2a4\ud0c0\uc77c","Subscript":"\uc544\ub798 \ucca8\uc790","Superscript":"\uc704 \ucca8\uc790","Switch to or from fullscreen mode":"\uc804\uccb4 \ud654\uba74 \ubaa8\ub4dc \uc804\ud658","Symbols":"\uae30\ud638","System Font":"\uc2dc\uc2a4\ud15c \uae00\uaf34","Table":"\ud45c","Table caption":"\ud45c \ucea1\uc158","Table properties":"\ud45c \uc18d\uc131","Table styles":"\ud45c \ubaa8\uc591","Template":"\ud15c\ud50c\ub9bf","Templates":"\ud15c\ud50c\ub9bf","Text":"\ud14d\uc2a4\ud2b8","Text color":"\uae00\uc790 \uc0c9","Text color {0}":"\ud14d\uc2a4\ud2b8 \uc0c9\uc0c1 {0}","Text to display":"\ud45c\uc2dc\ud560 \ud14d\uc2a4\ud2b8","The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?":'\uc785\ub825\ud558\uc2e0 URL\uc774 \uc774\uba54\uc77c \uc8fc\uc18c\uc778 \uac83 \uac19\uc2b5\ub2c8\ub2e4. "mailto:" \uc811\ub450\uc0ac\ub97c \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?',"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?":'\uc785\ub825\ud558\uc2e0 URL\uc774 \uc678\ubd80 \ub9c1\ud06c\uc778 \uac83 \uac19\uc2b5\ub2c8\ub2e4. "http://" \uc811\ub450\uc0ac\ub97c \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?',"The URL you entered seems to be an external link. Do you want to add the required https:// prefix?":'\uc785\ub825\ud558\uc2e0 URL\uc774 \uc678\ubd80 \ub9c1\ud06c\uc778 \uac83 \uac19\uc2b5\ub2c8\ub2e4. "https://" \uc811\ub450\uc0ac\ub97c \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?',"Title":"\uc81c\ubaa9","To open the popup, press Shift+Enter":"\ud31d\uc5c5\uc744 \uc5f4\ub824\uba74 Shift+Enter\ub97c \ub204\ub974\uc2ed\uc2dc\uc624.","Toggle accordion":"\uc544\ucf54\ub514\uc5b8 \ud1a0\uae00","Tools":"\ub3c4\uad6c","Top":"\uc704\ucabd \ub9de\ucda4","Travel and Places":"\uc5ec\ud589\uacfc \uc7a5\uc18c","Turquoise":"\uccad\ub85d\uc0c9","Underline":"\ubc11\uc904","Undo":"\uc2e4\ud589 \ucde8\uc18c","Upload":"\uc5c5\ub85c\ub4dc","Uploading image":"\uc774\ubbf8\uc9c0 \uc5c5\ub85c\ub4dc \uc911","Upper Alpha":"\uc54c\ud30c\ubcb3 \ub300\ubb38\uc790","Upper Roman":"\ub85c\ub9c8\uc790 \ub300\ubb38\uc790","Url":"URL","User Defined":"\uc0ac\uc6a9\uc790 \uc815\uc758","Valid":"\uc720\ud6a8\ud568","Version":"\ubc84\uc804","Vertical align":"\uc218\uc9c1 \uc815\ub82c","Vertical space":"\uc0c1\ud558 \uc5ec\ubc31","View":"\ubcf4\uae30","Visual aids":"\ud45c\uc758 \ud14c\ub450\ub9ac\ub97c \uc810\uc120\uc73c\ub85c \ud45c\uc2dc","Warn":"\uacbd\uace0","White":"\ud770\uc0c9","Width":"\ub108\ube44","Word count":"\ubb38\uc790 \uc218","Words":"\ub2e8\uc5b4 \uc218","Words: {0}":"\ub2e8\uc5b4 \uc218: {0}","Yellow":"\ub178\ub780\uc0c9","Yes":"\ub124","You are using {0}":"{0} \uc0ac\uc6a9 \uc911","You have unsaved changes are you sure you want to navigate away?":"\uc800\uc7a5\ud558\uc9c0 \uc54a\uc740 \uc815\ubcf4\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uc774 \ud398\uc774\uc9c0\ub97c \ub098\uac00\uc2dc\uaca0\uc2b5\ub2c8\uae4c?","Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.":"\ube0c\ub77c\uc6b0\uc800\uac00 \ud074\ub9bd\ubcf4\ub4dc \uc811\uadfc\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. Ctrl+X/C/V \ub2e8\ucd95\ud0a4\ub97c \uc774\uc6a9\ud574\uc8fc\uc138\uc694.","alignment":"\uc815\ub82c","austral sign":"\uc544\uc6b0\uc2a4\ud2b8\ub784 \uae30\ud638","cedi sign":"\uc138\ub514 \uae30\ud638","colon sign":"\ucf5c\ub860 \uae30\ud638","cruzeiro sign":"\ud06c\ub8e8\uc81c\uc774\ub85c \uae30\ud638","currency sign":"\ud1b5\ud654 \uae30\ud638","dollar sign":"\ub2ec\ub7ec \uae30\ud638","dong sign":"\ub3d9 \uae30\ud638","drachma sign":"\ub4dc\ub77c\ud06c\ub9c8 \uae30\ud638","euro-currency sign":"\uc720\ub85c\ud654 \uae30\ud638","example":"\uc608\uc81c","formatting":"\uc11c\uc2dd","french franc sign":"\ud504\ub791\uc2a4 \ud504\ub791 \uae30\ud638","german penny symbol":"\ub3c5\uc77c \ud398\ub2c8 \uae30\ud638","guarani sign":"\uacfc\ub77c\ub2c8 \uae30\ud638","history":"\uc774\ub825","hryvnia sign":"\uadf8\ub9ac\ube0c\ub098 \uae30\ud638","indentation":"\ub4e4\uc5ec\uc4f0\uae30","indian rupee sign":"\uc778\ub3c4 \ub8e8\ud53c \uae30\ud638","kip sign":"\ud0b5 \uae30\ud638","lira sign":"\ub9ac\ub77c \uae30\ud638","livre tournois sign":"\ub9ac\ube0c\ub974 \ud2b8\ub974\ub204\uc544 \uae30\ud638","manat sign":"\ub9c8\ub098\ud2b8 \uae30\ud638","mill sign":"\ubc00 \uae30\ud638","naira sign":"\ub098\uc774\ub77c \uae30\ud638","new sheqel sign":"\ub274 \uc138\ucf08 \uae30\ud638","nordic mark sign":"\ub178\ub974\ub515 \ub9c8\ub974\ud06c \uae30\ud638","peseta sign":"\ud398\uc138\ud0c0 \uae30\ud638","peso sign":"\ud398\uc18c \uae30\ud638","ruble sign":"\ub8e8\ube14 \uae30\ud638","rupee sign":"\ub8e8\ud53c \uae30\ud638","spesmilo sign":"\uc2a4\ud398\uc2a4\ubc00\ub85c \uae30\ud638","styles":"\uc2a4\ud0c0\uc77c","tenge sign":"\ud161\uac8c \uae30\ud638","tugrik sign":"\ud22c\uadf8\ub9ac\ud06c \uae30\ud638","turkish lira sign":"\ud130\ud0a4 \ub9ac\ub77c \uae30\ud638","won sign":"\uc6d0 \uae30\ud638","yen character":"\uc5d4 \uae30\ud638","yen/yuan character variant one":"\uc5d4/\uc704\uc548 \ubb38\uc790 \ubcc0\ud615","yuan character":"\uc704\uc548 \uae30\ud638","yuan character, in hong kong and taiwan":"\ub300\ub9cc \uc704\uc548 \uae30\ud638","{0} characters":"{0} \ubb38\uc790","{0} columns, {1} rows":"{0} \uc5f4, {1} \ud589","{0} words":"{0}\uac1c\uc758 \ub2e8\uc5b4"}); \ No newline at end of file diff --git a/apps/st/public/tinymce/license.txt b/apps/st/public/tinymce/license.txt new file mode 100644 index 000000000..b17fc9049 --- /dev/null +++ b/apps/st/public/tinymce/license.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/apps/st/public/tinymce/plugins/advlist/plugin.min.js b/apps/st/public/tinymce/plugins/advlist/plugin.min.js new file mode 100644 index 000000000..0bb6df847 --- /dev/null +++ b/apps/st/public/tinymce/plugins/advlist/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function f(t,e,n){t.execCommand("UL"===e?"InsertUnorderedList":"InsertOrderedList",!1,!1===n?null:{"list-style-type":n})}function i(t){return function(){return t}}function t(t){return t}function e(){return s}var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),g=tinymce.util.Tools.resolve("tinymce.util.Tools"),u=i(!1),l=i(!0),s={fold:function(t,e){return t()},isSome:u,isNone:l,getOr:t,getOrThunk:r,getOrDie:function(t){throw new Error(t||"error: getOrDie called on none.")},getOrNull:i(null),getOrUndefined:i(void 0),or:t,orThunk:r,map:e,each:function(){},bind:e,exists:u,forall:l,filter:function(){return s},toArray:function(){return[]},toString:i("none()")};function r(t){return t()}function d(t){return t&&/^(TH|TD)$/.test(t.nodeName)}function m(c,a){return function(s){function t(t){var e,n,r,o,i,u,l;s.setActive((e=c,r=a,i=-1!==(o=function(t,e){for(var n=0;n<t.length;n++)if(e(t[n]))return n;return-1}((n=t).parents,d))?n.parents.slice(0,o):n.parents,0<(u=g.grep(i,(l=e,function(t){return t&&/^(OL|UL|DL)$/.test(t.nodeName)&&(e=t,l.$.contains(l.getBody(),e));var e}))).length&&u[0].nodeName===r))}return c.on("NodeChange",t),function(){return c.off("NodeChange",t)}}}function c(t,e,n,r,o,i){var u,l,s,c,a,d;1<i.length?(c=r,a=o,d=i,(s=t).ui.registry.addSplitButton(e,{tooltip:n,icon:"OL"===a?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:function(t){t(g.map(d,function(t){return{type:"choiceitem",value:"default"===t?"":t,icon:"list-"+("OL"===a?"num":"bull")+"-"+("disc"===t||"decimal"===t?"default":t),text:t.replace(/\-/g," ").replace(/\b\w/g,function(t){return t.toUpperCase()})}}))},onAction:function(){return s.execCommand(c)},onItemAction:function(t,e){f(s,a,e)},select:function(e){var t,n=(t=s).dom.getParent(t.selection.getNode(),"ol,ul"),r=t.dom.getStyle(n,"listStyleType");return p(r).map(function(t){return e===t}).getOr(!1)},onSetup:m(s,a)})):(l=r,(u=t).ui.registry.addToggleButton(e,{active:!1,tooltip:n,icon:"OL"===o?"ordered-list":"unordered-list",onSetup:m(u,o),onAction:function(){return u.execCommand(l)}}))}var a=function(n){function t(){return o}function e(t){return t(n)}var r=i(n),o={fold:function(t,e){return e(n)},isSome:l,isNone:u,getOr:r,getOrThunk:r,getOrDie:r,getOrNull:r,getOrUndefined:r,or:t,orThunk:t,map:function(t){return a(t(n))},each:function(t){t(n)},bind:e,exists:e,forall:e,filter:function(t){return t(n)?o:s},toArray:function(){return[n]},toString:function(){return"some("+n+")"}};return o},p=function(t){return null==t?s:a(t)};n.add("advlist",function(t){var n,e,r,o;t.hasPlugin("lists")?(c(e=t,"numlist","Numbered list","InsertOrderedList","OL",(r=e.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman"))?r.split(/[ ,]/):[]),c(e,"bullist","Bullet list","InsertUnorderedList","UL",(o=e.getParam("advlist_bullet_styles","default,circle,square"))?o.split(/[ ,]/):[]),(n=t).addCommand("ApplyUnorderedListStyle",function(t,e){f(n,"UL",e["list-style-type"])}),n.addCommand("ApplyOrderedListStyle",function(t,e){f(n,"OL",e["list-style-type"])})):console.error("Please use the Lists plugin together with the Advanced List plugin.")})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/anchor/plugin.min.js b/apps/st/public/tinymce/plugins/anchor/plugin.min.js new file mode 100644 index 000000000..fc18d1adb --- /dev/null +++ b/apps/st/public/tinymce/plugins/anchor/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function a(e){return e.getAttribute("id")||e.getAttribute("name")||""}function c(e){return e&&"a"===e.nodeName.toLowerCase()&&!e.getAttribute("href")&&""!==a(e)}function d(e){return e.dom.getParent(e.selection.getStart(),l)}function r(e,t){var o,a,n,r,i,l=d(e);l?(n=e,r=t,(i=l).removeAttribute("name"),i.id=r,n.addVisual(),n.undoManager.add()):(a=t,(o=e).undoManager.transact(function(){var e,n;o.getParam("allow_html_in_named_anchor",!1,"boolean")||o.selection.collapse(!0),o.selection.isCollapsed()?o.insertContent(o.dom.createHTML("a",{id:a})):(n=(e=o).dom,u(n).walk(e.selection.getRng(),function(e){s.each(e,function(e){var t;c(t=e)&&!t.firstChild&&n.remove(e,!1)})}),o.formatter.remove("namedAnchor",null,null,!0),o.formatter.apply("namedAnchor",{value:a}),o.addVisual())})),e.focus()}function i(r){return function(e){for(var t,n=0;n<e.length;n++){var o=e[n],a=void 0;!(a=t=o)||a.attr("href")||!a.attr("id")&&!a.attr("name")||t.firstChild||o.attr("contenteditable",r)}}}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),u=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),s=tinymce.util.Tools.resolve("tinymce.util.Tools"),l="a:not([href])";e.add("anchor",function(e){var t,n,o;(t=e).on("PreInit",function(){t.parser.addNodeFilter("a",i("false")),t.serializer.addNodeFilter("a",i(null))}),(n=e).addCommand("mceAnchor",function(){var o,e,t;t=(e=d(o=n))?a(e):"",o.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:function(e){var t=o,n=e.getData().id;(/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(n)?(r(t,n),0):(t.windowManager.alert("Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),1))||e.close()}})}),(o=e).ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:function(){return o.execCommand("mceAnchor")},onSetup:function(e){return o.selection.selectorChangedWithUnbind("a:not([href])",e.setActive).unbind}}),o.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:function(){return o.execCommand("mceAnchor")}}),e.on("PreInit",function(){e.formatter.register("namedAnchor",{inline:"a",selector:l,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:c})})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/autolink/plugin.min.js b/apps/st/public/tinymce/plugins/autolink/plugin.min.js new file mode 100644 index 000000000..bc845b55d --- /dev/null +++ b/apps/st/public/tinymce/plugins/autolink/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function k(e){return/^[(\[{ \u00a0]$/.test(e)}function w(e){return 3===e.nodeType}function i(e){return 1===e.nodeType}function o(e,t){var n;return t<0&&(t=0),!w(e)||(n=e.data.length)<t&&(t=n),t}function y(e,t,n){!i(t)||t.hasChildNodes()?e.setStart(t,o(t,n)):e.setStartBefore(t)}function v(e,t,n){!i(t)||t.hasChildNodes()?e.setEnd(t,o(t,n)):e.setEndAfter(t)}function r(e,t){var n,i,o,r,a,f=e.getParam("autolink_pattern",A),s=e.getParam("default_link_target",!1);if(null===e.dom.getParent(e.selection.getNode(),"a[href]")){var d=e.selection.getRng().cloneRange();if(d.startOffset<5){if(!(r=d.endContainer.previousSibling)){if(!d.endContainer.firstChild||!d.endContainer.firstChild.nextSibling)return;r=d.endContainer.firstChild.nextSibling}if(y(d,r,a=r.length),v(d,r,a),d.endOffset<5)return;n=d.endOffset,i=r}else{if(!w(i=d.endContainer)&&i.firstChild){for(;!w(i)&&i.firstChild;)i=i.firstChild;w(i)&&(y(d,i,0),v(d,i,i.nodeValue.length))}n=1===d.endOffset?2:d.endOffset-1-t}for(var l=n;y(d,i,2<=n?n-2:0),v(d,i,1<=n?n-1:0),--n,!k(d.toString())&&0<=n-2;);k(d.toString())?(y(d,i,n),v(d,i,l),n+=1):(0===d.startOffset?y(d,i,0):y(d,i,n),v(d,i,l)),u=d.toString(),/[?!,.;:]/.test(u.charAt(u.length-1))&&v(d,i,l-1);var u,c,g,h,C=(u=d.toString().trim()).match(f),m=e.getParam("link_default_protocol","http","string");C&&((g=c=C[0]).length>=(h="www.").length&&g.substr(0,0+h.length)===h?c=m+"://"+c:-1===c.indexOf("@")||/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(c)||(c="mailto:"+c),o=e.selection.getBookmark(),e.selection.setRng(d),e.execCommand("createlink",!1,c),!1!==s&&e.dom.setAttrib(e.selection.getNode(),"target",s),e.selection.moveToBookmark(o),e.nodeChanged())}}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env"),A=new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i");e.add("autolink",function(e){var t,n;(t=e).on("keydown",function(e){if(13===e.keyCode)return r(t,-1)}),a.browser.isIE()?t.on("focus",function(){if(!n){n=!0;try{t.execCommand("AutoUrlDetect",!1,!0)}catch(e){}}}):(t.on("keypress",function(e){if(41===e.keyCode||93===e.keyCode||125===e.keyCode)return r(t,-1)}),t.on("keyup",function(e){if(32===e.keyCode)return r(t,0)}))})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/autoresize/plugin.min.js b/apps/st/public/tinymce/plugins/autoresize/plugin.min.js new file mode 100644 index 000000000..8a73683c1 --- /dev/null +++ b/apps/st/public/tinymce/plugins/autoresize/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function y(e){return e.getParam("min_height",e.getElement().offsetHeight,"number")}function p(e,t){var n=e.getBody();n&&(n.style.overflowY=t?"":"hidden",t||(n.scrollTop=0))}function v(e,t,n,i){var o=parseInt(e.getStyle(t,n,i),10);return isNaN(o)?0:o}var l=Object.hasOwnProperty,e=tinymce.util.Tools.resolve("tinymce.PluginManager"),b=tinymce.util.Tools.resolve("tinymce.Env"),r=tinymce.util.Tools.resolve("tinymce.util.Delay"),u=function(e,t,n,i,o){r.setEditorTimeout(e,function(){C(e,t),n--?u(e,t,n,i,o):o&&o()},i)},C=function(e,t,n){var i,o,r,s,a,l,u,g,c,m,f,d=e.dom,h=e.getDoc();h&&(e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen()?p(e,!0):(i=h.documentElement,o=e.getParam("autoresize_bottom_margin",50,"number"),r=y(e),s=v(d,i,"margin-top",!0),a=v(d,i,"margin-bottom",!0),(l=(l=i.offsetHeight+s+a+o)<0?0:l)+(u=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight)>y(e)&&(r=l+u),(g=e.getParam("max_height",0,"number"))&&g<r?(r=g,p(e,!0)):p(e,!1),r!==t.get()&&(c=r-t.get(),d.setStyle(e.getContainer(),"height",r+"px"),t.set(r),e.fire("ResizeEditor"),b.browser.isSafari()&&b.mac&&(m=e.getWin()).scrollTo(m.pageXOffset,m.pageYOffset),!e.hasFocus()||"setcontent"!==(null==(f=n)?void 0:f.type.toLowerCase())||!0!==f.selection&&!0!==f.paste||e.selection.scrollIntoView(),b.webkit&&c<0&&C(e,t,n))))};e.add("autoresize",function(e){var t,n,i,o,r,s,a=e.settings;l.call(a,"resize")||(e.settings.resize=!1),e.inline||(s=0,r=t={get:function(){return s},set:function(e){s=e}},(o=e).addCommand("mceAutoResize",function(){C(o,r)}),i=t,(n=e).on("init",function(){var e=n.getParam("autoresize_overflow_padding",1,"number"),t=n.dom;t.setStyles(n.getDoc().documentElement,{height:"auto"}),t.setStyles(n.getBody(),{paddingLeft:e,paddingRight:e,"min-height":0})}),n.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",function(e){C(n,i,e)}),n.getParam("autoresize_on_init",!0,"boolean")&&n.on("init",function(){u(n,i,20,100,function(){u(n,i,5,1e3)})}))})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/autosave/plugin.min.js b/apps/st/public/tinymce/plugins/autosave/plugin.min.js new file mode 100644 index 000000000..cb8c15877 --- /dev/null +++ b/apps/st/public/tinymce/plugins/autosave/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function o(t,e){var r=t||e,n=/^(\d+)([ms]?)$/.exec(""+r);return(n[2]?{s:1e3,m:6e4}[n[2]]:1)*parseInt(r,10)}function n(t){var e=document.location;return t.getParam("autosave_prefix","tinymce-autosave-{path}{query}{hash}-{id}-").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)}function i(t,e){if(a(e))return t.dom.isEmpty(t.getBody());var r=d.trim(e);if(""===r)return!0;var n=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(n)}function u(t){var e=parseInt(v.getItem(n(t)+"time"),10)||0;return!((new Date).getTime()-e>o(t.getParam("autosave_retention"),"20m")&&(g(t,!1),1))}function s(t){var e=n(t);!i(t)&&t.isDirty()&&(v.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),v.setItem(e+"time",(new Date).getTime().toString()),t.fire("StoreDraft"))}function f(t){var e=n(t);u(t)&&(t.setContent(v.getItem(e+"draft"),{format:"raw"}),t.fire("RestoreDraft"))}function c(t){t.undoManager.transact(function(){f(t),g(t)}),t.focus()}function m(r){return function(t){function e(){return t.setDisabled(!u(r))}return t.setDisabled(!u(r)),r.on("StoreDraft RestoreDraft RemoveDraft",e),function(){return r.off("StoreDraft RestoreDraft RemoveDraft",e)}}}var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=function(t){return void 0===t},l=tinymce.util.Tools.resolve("tinymce.util.Delay"),v=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),d=tinymce.util.Tools.resolve("tinymce.util.Tools"),g=function(t,e){var r=n(t);v.removeItem(r+"draft"),v.removeItem(r+"time"),!1!==e&&t.fire("RemoveDraft")},y=tinymce.util.Tools.resolve("tinymce.EditorManager");t.add("autosave",function(t){var e,r,n,a;return t.editorManager.on("BeforeUnload",function(t){var e;d.each(y.get(),function(t){t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&t.getParam("autosave_ask_before_unload",!0)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))}),e&&(t.preventDefault(),t.returnValue=e)}),n=e=t,a=o(n.getParam("autosave_interval"),"30s"),l.setEditorInterval(n,function(){s(n)},a),e.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:function(){c(e)},onSetup:m(e)}),e.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:function(){c(e)},onSetup:m(e)}),t.on("init",function(){t.getParam("autosave_restore_when_empty",!1)&&t.dom.isEmpty(t.getBody())&&f(t)}),r=t,{hasDraft:function(){return u(r)},storeDraft:function(){return s(r)},restoreDraft:function(){return f(r)},removeDraft:function(t){return g(r,t)},isEmpty:function(t){return i(r,t)}}})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/bbcode/plugin.min.js b/apps/st/public/tinymce/plugins/bbcode/plugin.min.js new file mode 100644 index 000000000..8bfd98ff2 --- /dev/null +++ b/apps/st/public/tinymce/plugins/bbcode/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function i(t){function o(o,e){t=t.replace(o,e)}return t=n.trim(t),o(/\n/gi,"<br />"),o(/\[b\]/gi,"<strong>"),o(/\[\/b\]/gi,"</strong>"),o(/\[i\]/gi,"<em>"),o(/\[\/i\]/gi,"</em>"),o(/\[u\]/gi,"<u>"),o(/\[\/u\]/gi,"</u>"),o(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'<a href="$1">$2</a>'),o(/\[url\](.*?)\[\/url\]/gi,'<a href="$1">$1</a>'),o(/\[img\](.*?)\[\/img\]/gi,'<img src="$1" />'),o(/\[color=(.*?)\](.*?)\[\/color\]/gi,'<font color="$1">$2</font>'),o(/\[code\](.*?)\[\/code\]/gi,'<span class="codeStyle">$1</span> '),o(/\[quote.*?\](.*?)\[\/quote\]/gi,'<span class="quoteStyle">$1</span> '),t}var o=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.util.Tools");o.add("bbcode",function(o){o.on("BeforeSetContent",function(o){o.content=i(o.content)}),o.on("PostProcess",function(o){function e(o,e){t=t.replace(o,e)}var t;o.set&&(o.content=i(o.content)),o.get&&(o.content=(t=o.content,t=n.trim(t),e(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]"),e(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]"),e(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]"),e(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]"),e(/<font>(.*?)<\/font>/gi,"$1"),e(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]"),e(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]"),e(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]"),e(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),e(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),e(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),e(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),e(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),e(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),e(/<\/(strong|b)>/gi,"[/b]"),e(/<(strong|b)>/gi,"[b]"),e(/<\/(em|i)>/gi,"[/i]"),e(/<(em|i)>/gi,"[i]"),e(/<\/u>/gi,"[/u]"),e(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]"),e(/<u>/gi,"[u]"),e(/<blockquote[^>]*>/gi,"[quote]"),e(/<\/blockquote>/gi,"[/quote]"),e(/<br \/>/gi,"\n"),e(/<br\/>/gi,"\n"),e(/<br>/gi,"\n"),e(/<p>/gi,""),e(/<\/p>/gi,"\n"),e(/ |\u00a0/gi," "),e(/"/gi,'"'),e(/</gi,"<"),e(/>/gi,">"),e(/&/gi,"&"),t))})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/charmap/plugin.min.js b/apps/st/public/tinymce/plugins/charmap/plugin.min.js new file mode 100644 index 000000000..de781ef9a --- /dev/null +++ b/apps/st/public/tinymce/plugins/charmap/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function l(e,r){var n=e.fire("insertCustomChar",{chr:r}).chr;e.execCommand("mceInsertContent",!1,n)}function i(e){return function(){return e}}function e(e){return e}function r(){return c}var t,g,n=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=function(e){return n=typeof(r=e),(null===r?"null":"object"==n&&(Array.prototype.isPrototypeOf(r)||r.constructor&&"Array"===r.constructor.name)?"array":"object"==n&&(String.prototype.isPrototypeOf(r)||r.constructor&&"String"===r.constructor.name)?"string":n)===t;var r,n},m=i(!(t="array")),o=i(!(g=null)),c={fold:function(e,r){return e()},isSome:m,isNone:o,getOr:e,getOrThunk:u,getOrDie:function(e){throw new Error(e||"error: getOrDie called on none.")},getOrNull:i(null),getOrUndefined:i(void 0),or:e,orThunk:u,map:r,each:function(){},bind:r,exists:m,forall:o,filter:function(){return c},toArray:function(){return[]},toString:i("none()")};function u(e){return e()}function f(e,r){for(var n=e.length,t=new Array(n),a=0;a<n;a++){var i=e[a];t[a]=r(i,a)}return t}function s(e){return A(e)?x.grep(e,function(e){return A(e)&&2===e.length}):"function"==typeof e?e():[]}function h(e){var r=function(e,r){var n=e.getParam("charmap");n&&(r=[{name:O,characters:s(n)}]);var t=e.getParam("charmap_append");if(t){var a=x.grep(r,function(e){return e.name===O});return a.length?(a[0].characters=[].concat(a[0].characters).concat(s(t)),r):r.concat({name:O,characters:s(t)})}return r}(e,[{name:"Currency",characters:[[36,"dollar sign"],[162,"cent sign"],[8364,"euro sign"],[163,"pound sign"],[165,"yen sign"],[164,"currency sign"],[8352,"euro-currency sign"],[8353,"colon sign"],[8354,"cruzeiro sign"],[8355,"french franc sign"],[8356,"lira sign"],[8357,"mill sign"],[8358,"naira sign"],[8359,"peseta sign"],[8360,"rupee sign"],[8361,"won sign"],[8362,"new sheqel sign"],[8363,"dong sign"],[8365,"kip sign"],[8366,"tugrik sign"],[8367,"drachma sign"],[8368,"german penny symbol"],[8369,"peso sign"],[8370,"guarani sign"],[8371,"austral sign"],[8372,"hryvnia sign"],[8373,"cedi sign"],[8374,"livre tournois sign"],[8375,"spesmilo sign"],[8376,"tenge sign"],[8377,"indian rupee sign"],[8378,"turkish lira sign"],[8379,"nordic mark sign"],[8380,"manat sign"],[8381,"ruble sign"],[20870,"yen character"],[20803,"yuan character"],[22291,"yuan character, in hong kong and taiwan"],[22278,"yen/yuan character variant one"]]},{name:"Text",characters:[[169,"copyright sign"],[174,"registered sign"],[8482,"trade mark sign"],[8240,"per mille sign"],[181,"micro sign"],[183,"middle dot"],[8226,"bullet"],[8230,"three dot leader"],[8242,"minutes / feet"],[8243,"seconds / inches"],[167,"section sign"],[182,"paragraph sign"],[223,"sharp s / ess-zed"]]},{name:"Quotations",characters:[[8249,"single left-pointing angle quotation mark"],[8250,"single right-pointing angle quotation mark"],[171,"left pointing guillemet"],[187,"right pointing guillemet"],[8216,"left single quotation mark"],[8217,"right single quotation mark"],[8220,"left double quotation mark"],[8221,"right double quotation mark"],[8218,"single low-9 quotation mark"],[8222,"double low-9 quotation mark"],[60,"less-than sign"],[62,"greater-than sign"],[8804,"less-than or equal to"],[8805,"greater-than or equal to"],[8211,"en dash"],[8212,"em dash"],[175,"macron"],[8254,"overline"],[164,"currency sign"],[166,"broken bar"],[168,"diaeresis"],[161,"inverted exclamation mark"],[191,"turned question mark"],[710,"circumflex accent"],[732,"small tilde"],[176,"degree sign"],[8722,"minus sign"],[177,"plus-minus sign"],[247,"division sign"],[8260,"fraction slash"],[215,"multiplication sign"],[185,"superscript one"],[178,"superscript two"],[179,"superscript three"],[188,"fraction one quarter"],[189,"fraction one half"],[190,"fraction three quarters"]]},{name:"Mathematical",characters:[[402,"function / florin"],[8747,"integral"],[8721,"n-ary sumation"],[8734,"infinity"],[8730,"square root"],[8764,"similar to"],[8773,"approximately equal to"],[8776,"almost equal to"],[8800,"not equal to"],[8801,"identical to"],[8712,"element of"],[8713,"not an element of"],[8715,"contains as member"],[8719,"n-ary product"],[8743,"logical and"],[8744,"logical or"],[172,"not sign"],[8745,"intersection"],[8746,"union"],[8706,"partial differential"],[8704,"for all"],[8707,"there exists"],[8709,"diameter"],[8711,"backward difference"],[8727,"asterisk operator"],[8733,"proportional to"],[8736,"angle"]]},{name:"Extended Latin",characters:[[192,"A - grave"],[193,"A - acute"],[194,"A - circumflex"],[195,"A - tilde"],[196,"A - diaeresis"],[197,"A - ring above"],[256,"A - macron"],[198,"ligature AE"],[199,"C - cedilla"],[200,"E - grave"],[201,"E - acute"],[202,"E - circumflex"],[203,"E - diaeresis"],[274,"E - macron"],[204,"I - grave"],[205,"I - acute"],[206,"I - circumflex"],[207,"I - diaeresis"],[298,"I - macron"],[208,"ETH"],[209,"N - tilde"],[210,"O - grave"],[211,"O - acute"],[212,"O - circumflex"],[213,"O - tilde"],[214,"O - diaeresis"],[216,"O - slash"],[332,"O - macron"],[338,"ligature OE"],[352,"S - caron"],[217,"U - grave"],[218,"U - acute"],[219,"U - circumflex"],[220,"U - diaeresis"],[362,"U - macron"],[221,"Y - acute"],[376,"Y - diaeresis"],[562,"Y - macron"],[222,"THORN"],[224,"a - grave"],[225,"a - acute"],[226,"a - circumflex"],[227,"a - tilde"],[228,"a - diaeresis"],[229,"a - ring above"],[257,"a - macron"],[230,"ligature ae"],[231,"c - cedilla"],[232,"e - grave"],[233,"e - acute"],[234,"e - circumflex"],[235,"e - diaeresis"],[275,"e - macron"],[236,"i - grave"],[237,"i - acute"],[238,"i - circumflex"],[239,"i - diaeresis"],[299,"i - macron"],[240,"eth"],[241,"n - tilde"],[242,"o - grave"],[243,"o - acute"],[244,"o - circumflex"],[245,"o - tilde"],[246,"o - diaeresis"],[248,"o slash"],[333,"o macron"],[339,"ligature oe"],[353,"s - caron"],[249,"u - grave"],[250,"u - acute"],[251,"u - circumflex"],[252,"u - diaeresis"],[363,"u - macron"],[253,"y - acute"],[254,"thorn"],[255,"y - diaeresis"],[563,"y - macron"],[913,"Alpha"],[914,"Beta"],[915,"Gamma"],[916,"Delta"],[917,"Epsilon"],[918,"Zeta"],[919,"Eta"],[920,"Theta"],[921,"Iota"],[922,"Kappa"],[923,"Lambda"],[924,"Mu"],[925,"Nu"],[926,"Xi"],[927,"Omicron"],[928,"Pi"],[929,"Rho"],[931,"Sigma"],[932,"Tau"],[933,"Upsilon"],[934,"Phi"],[935,"Chi"],[936,"Psi"],[937,"Omega"],[945,"alpha"],[946,"beta"],[947,"gamma"],[948,"delta"],[949,"epsilon"],[950,"zeta"],[951,"eta"],[952,"theta"],[953,"iota"],[954,"kappa"],[955,"lambda"],[956,"mu"],[957,"nu"],[958,"xi"],[959,"omicron"],[960,"pi"],[961,"rho"],[962,"final sigma"],[963,"sigma"],[964,"tau"],[965,"upsilon"],[966,"phi"],[967,"chi"],[968,"psi"],[969,"omega"]]},{name:"Symbols",characters:[[8501,"alef symbol"],[982,"pi symbol"],[8476,"real part symbol"],[978,"upsilon - hook symbol"],[8472,"Weierstrass p"],[8465,"imaginary part"]]},{name:"Arrows",characters:[[8592,"leftwards arrow"],[8593,"upwards arrow"],[8594,"rightwards arrow"],[8595,"downwards arrow"],[8596,"left right arrow"],[8629,"carriage return"],[8656,"leftwards double arrow"],[8657,"upwards double arrow"],[8658,"rightwards double arrow"],[8659,"downwards double arrow"],[8660,"left right double arrow"],[8756,"therefore"],[8834,"subset of"],[8835,"superset of"],[8836,"not a subset of"],[8838,"subset of or equal to"],[8839,"superset of or equal to"],[8853,"circled plus"],[8855,"circled times"],[8869,"perpendicular"],[8901,"dot operator"],[8968,"left ceiling"],[8969,"right ceiling"],[8970,"left floor"],[8971,"right floor"],[9001,"left-pointing angle bracket"],[9002,"right-pointing angle bracket"],[9674,"lozenge"],[9824,"black spade suit"],[9827,"black club suit"],[9829,"black heart suit"],[9830,"black diamond suit"],[8194,"en space"],[8195,"em space"],[8201,"thin space"],[8204,"zero width non-joiner"],[8205,"zero width joiner"],[8206,"left-to-right mark"],[8207,"right-to-left mark"]]}]);return 1<r.length?[{name:"All",characters:function(e){for(var r=[],n=0,t=e.length;n<t;++n){if(!a(e[n]))throw new Error("Arr.flatten item "+n+" was not an array, input: "+e);C.apply(r,e[n])}return r}(f(r,function(e){return e.characters}))}].concat(r):r}function p(e){var r=e;return{get:function(){return r},set:function(e){r=e}}}function d(e,r){return-1!==e.indexOf(r)}function y(){for(var e=[],r=0;r<arguments.length;r++)e[r]=arguments[r];if(q)return q.apply(void 0,e);for(var n=[],t="",a=0,i=e.length;a!==i;++a){var o=+e[a];if(!(o<1114111&&o>>>0===o))throw RangeError("Invalid code point: "+o);16383<=(o<=65535?n.push(o):(o-=65536,n.push(55296+(o>>10),o%1024+56320)))&&(t+=String.fromCharCode.apply(null,n),n.length=0)}return t+String.fromCharCode.apply(null,n)}function v(e,r){var c=[],u=r.toLowerCase();return function(e){for(var r,n,t,a,i=0,o=e.length;i<o;i++)r=e[i],a=t=n=a=t=n=void 0,n=r[0],t=r[1],a=u,(d(y(n).toLowerCase(),a)||d(t.toLowerCase(),a)||d(t.toLowerCase().replace(/\s+/g,""),a))&&c.push(r)}(e.characters),f(c,function(e){return{text:e[1],value:y(e[0]),icon:y(e[0])}})}function w(n,a){function r(){return[{label:"Search",type:"input",name:S},{type:"collection",name:"results"}]}var t,i,o=1===a.length?p(O):p("All"),c=(t=function(e){var r=e.getData().pattern,n=e,t=r;(function(e,r,n){for(var t=0,a=e.length;t<a;t++){var i=e[t];if(r(i,t))return k.some(i);if(n(i,t))break}return k.none()})(a,function(e){return e.name===o.get()},m).each(function(e){var r=v(e,t);n.setData({results:r})})},i=null,{cancel:u,throttle:function(){for(var e=[],r=0;r<arguments.length;r++)e[r]=arguments[r];u(),i=setTimeout(function(){i=null,t.apply(null,e)},40)}});function u(){g===i||(clearTimeout(i),i=null)}var e=1===a.length?{type:"panel",items:r()}:{type:"tabpanel",tabs:f(a,function(e){return{title:e.name,name:e.name,items:r()}})},s={pattern:"",results:v(a[0],"")};n.windowManager.open({title:"Special Character",size:"normal",body:e,buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:s,onAction:function(e,r){"results"===r.name&&(l(n,r.value),e.close())},onTabChange:function(e,r){o.set(r.newTabName),c.throttle(e)},onChange:function(e,r){r.name===S&&c.throttle(e)}}).focus(S)}var b=function(n){function e(){return a}function r(e){return e(n)}var t=i(n),a={fold:function(e,r){return r(n)},isSome:o,isNone:m,getOr:t,getOrThunk:t,getOrDie:t,getOrNull:t,getOrUndefined:t,or:e,orThunk:e,map:function(e){return b(e(n))},each:function(e){e(n)},bind:r,exists:r,forall:r,filter:function(e){return e(n)?a:c},toArray:function(){return[n]},toString:function(){return"some("+n+")"}};return a},k={some:b,none:r,from:function(e){return null==e?c:b(e)}},C=Array.prototype.push,x=tinymce.util.Tools.resolve("tinymce.util.Tools"),A=x.isArray,O="User Defined",q=String.fromCodePoint,S="pattern",T=tinymce.util.Tools.resolve("tinymce.util.Promise");n.add("charmap",function(e){var r,n,t,a,i,o=h(e),c=o;return(r=e).addCommand("mceShowCharmap",function(){w(r,c)}),(n=e).ui.registry.addButton("charmap",{icon:"insert-character",tooltip:"Special character",onAction:function(){return n.execCommand("mceShowCharmap")}}),n.ui.registry.addMenuItem("charmap",{icon:"insert-character",text:"Special character...",onAction:function(){return n.execCommand("mceShowCharmap")}}),t=e,a=o[0],t.ui.registry.addAutocompleter("charmap",{ch:":",columns:"auto",minChars:2,fetch:function(n,e){return new T(function(e,r){e(v(a,n))})},onAction:function(e,r,n){t.selection.setRng(r),t.insertContent(n),e.hide()}}),i=e,{getCharMap:function(){return h(i)},insertChar:function(e){l(i,e)}}})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/code/plugin.min.js b/apps/st/public/tinymce/plugins/code/plugin.min.js new file mode 100644 index 000000000..f2980180b --- /dev/null +++ b/apps/st/public/tinymce/plugins/code/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",function(e){var t,o;function n(){return o.execCommand("mceCodeEditor")}return(t=e).addCommand("mceCodeEditor",function(){var n,e;e=(n=t).getContent({source_view:!0}),n.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:e},onSubmit:function(e){var t=n,o=e.getData().code;t.focus(),t.undoManager.transact(function(){t.setContent(o)}),t.selection.setCursorLocation(),t.nodeChanged(),e.close()}})}),(o=e).ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:n}),o.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:n}),{}})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/codesample/plugin.min.js b/apps/st/public/tinymce/plugins/codesample/plugin.min.js new file mode 100644 index 000000000..f6f692f4e --- /dev/null +++ b/apps/st/public/tinymce/plugins/codesample/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function o(e){return function(){return e}}function e(e){return e}function n(){return l}var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),s=o(!1),i=o(!0),l={fold:function(e,n){return e()},isSome:s,isNone:i,getOr:e,getOrThunk:a,getOrDie:function(e){throw new Error(e||"error: getOrDie called on none.")},getOrNull:o(null),getOrUndefined:o(void 0),or:e,orThunk:a,map:n,each:function(){},bind:n,exists:s,forall:i,filter:function(){return l},toArray:function(){return[]},toString:o("none()")};function a(e){return e()}function u(e){return e&&"PRE"===e.nodeName&&-1!==e.className.indexOf("language-")}function c(t){return function(e,n){return t(n)}}var d=function(t){function e(){return r}function n(e){return e(t)}var a=o(t),r={fold:function(e,n){return n(t)},isSome:i,isNone:s,getOr:a,getOrThunk:a,getOrDie:a,getOrNull:a,getOrUndefined:a,or:e,orThunk:e,map:function(e){return d(e(t))},each:function(e){e(t)},bind:n,exists:n,forall:n,filter:function(e){return e(t)?r:l},toArray:function(){return[t]},toString:function(){return"some("+t+")"}};return r},p={some:d,none:n,from:function(e){return null==e?l:d(e)}},g=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),r="undefined"!=typeof window?window:Function("return this;")(),b={},m={exports:b},f={};function h(e){return r.Prism&&e.getParam("codesample_global_prismjs",!1,"boolean")?r.Prism:k}function y(e){var n=e.selection?e.selection.getNode():null;return u(n)?p.some(n):p.none()}function w(s){var t,e,n=s.getParam("codesample_languages")||[{text:"HTML/XML",value:"markup"},{text:"JavaScript",value:"javascript"},{text:"CSS",value:"css"},{text:"PHP",value:"php"},{text:"Ruby",value:"ruby"},{text:"Python",value:"python"},{text:"Java",value:"java"},{text:"C",value:"c"},{text:"C#",value:"csharp"},{text:"C++",value:"cpp"}],a=(0<(e=n).length?p.some(e[0]):p.none()).fold(o(""),function(e){return e.value}),r=(t=a,y(s).fold(function(){return t},function(e){var n=e.className.match(/language-(\w+)/);return n?n[1]:t})),i=y(s).fold(o(""),function(e){return e.textContent});s.windowManager.open({title:"Insert/Edit Code Sample",size:"large",body:{type:"panel",items:[{type:"selectbox",name:"language",label:"Language",items:n},{type:"textarea",name:"code",label:"Code view"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{language:r,code:i},onSubmit:function(e){var n=e.getData(),t=s,a=n.language,r=n.code;t.undoManager.transact(function(){var e=y(t);return r=g.DOM.encode(r),e.fold(function(){t.insertContent('<pre id="__new" class="language-'+a+'">'+r+"</pre>"),t.selection.select(t.$("#__new").removeAttr("id")[0])},function(e){t.dom.setAttrib(e,"class","language-"+a),e.innerHTML=r,h(t).highlightElement(e),t.selection.select(e)})}),e.close()}})}!function(e,n){var t,a,r=window.Prism;window.Prism={manual:!0},t=this,a=function(){var e,n,h,t,a,r,s,i,o,l,u="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:f,c={exports:{}};function y(e,n){return"___"+e.toUpperCase()+n+"___"}return e=c,n=function(u){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,e={},j={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof T?new T(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,a){var r,n;switch(a=a||{},j.util.type(e)){case"Object":if(n=j.util.objId(e),a[n])return a[n];for(var s in r={},a[n]=r,e)e.hasOwnProperty(s)&&(r[s]=t(e[s],a));return r;case"Array":return n=j.util.objId(e),a[n]||(r=[],a[n]=r,e.forEach(function(e,n){r[n]=t(e,a)}),r);default:return e}},getLanguage:function(e){for(;e;){var n=t.exec(e.className);if(n)return n[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,n){e.className=e.className.replace(RegExp(t.source,"gi"),""),e.classList.add("language-"+n)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(e.stack)||[])[1];if(n){var t,a=document.getElementsByTagName("script");for(t in a)if(a[t].src==n)return a[t]}return null}},isActive:function(e,n,t){for(var a="no-"+n;e;){var r=e.classList;if(r.contains(n))return!0;if(r.contains(a))return!1;e=e.parentElement}return!!t}},languages:{plain:e,plaintext:e,text:e,txt:e,extend:function(e,n){var t,a=j.util.clone(j.languages[e]);for(t in n)a[t]=n[t];return a},insertBefore:function(t,e,n,a){var r,s=(a=a||j.languages)[t],i={};for(r in s)if(s.hasOwnProperty(r)){if(r==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(r)||(i[r]=s[r])}var l=a[t];return a[t]=i,j.languages.DFS(j.languages,function(e,n){n===l&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,a,r){r=r||{};var s,i,o,l=j.util.objId;for(s in n)n.hasOwnProperty(s)&&(t.call(n,s,n[s],a||s),i=n[s],"Object"!==(o=j.util.type(i))||r[l(i)]?"Array"!==o||r[l(i)]||(r[l(i)]=!0,e(i,t,s,r)):(r[l(i)]=!0,e(i,t,null,r)))}},plugins:{},highlightAll:function(e,n){j.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var a={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};j.hooks.run("before-highlightall",a),a.elements=Array.prototype.slice.apply(a.container.querySelectorAll(a.selector)),j.hooks.run("before-all-elements-highlight",a);for(var r,s=0;r=a.elements[s++];)j.highlightElement(r,!0===n,a.callback)},highlightElement:function(e,n,t){var a=j.util.getLanguage(e),r=j.languages[a];j.util.setLanguage(e,a);var s=e.parentElement;s&&"pre"===s.nodeName.toLowerCase()&&j.util.setLanguage(s,a);var i,o={element:e,language:a,grammar:r,code:e.textContent};function l(e){o.highlightedCode=e,j.hooks.run("before-insert",o),o.element.innerHTML=o.highlightedCode,j.hooks.run("after-highlight",o),j.hooks.run("complete",o),t&&t.call(o.element)}if(j.hooks.run("before-sanity-check",o),(s=o.element.parentElement)&&"pre"===s.nodeName.toLowerCase()&&!s.hasAttribute("tabindex")&&s.setAttribute("tabindex","0"),!o.code)return j.hooks.run("complete",o),void(t&&t.call(o.element));j.hooks.run("before-highlight",o),o.grammar?n&&u.Worker?((i=new Worker(j.filename)).onmessage=function(e){l(e.data)},i.postMessage(JSON.stringify({language:o.language,code:o.code,immediateClose:!0}))):l(j.highlight(o.code,o.grammar,o.language)):l(j.util.encode(o.code))},highlight:function(e,n,t){var a={code:e,grammar:n,language:t};if(j.hooks.run("before-tokenize",a),!a.grammar)throw new Error('The language "'+a.language+'" has no grammar.');return a.tokens=j.tokenize(a.code,a.grammar),j.hooks.run("after-tokenize",a),T.stringify(j.util.encode(a.tokens),a.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var a in t)n[a]=t[a];delete n.rest}var r=new s;return O(r,r.head,e),function e(n,t,a,r,s,i){for(var o in a)if(a.hasOwnProperty(o)&&a[o])for(var l=a[o],l=Array.isArray(l)?l:[l],u=0;u<l.length;++u){if(i&&i.cause==o+","+u)return;var c,d=l[u],p=d.inside,g=!!d.lookbehind,b=!!d.greedy,m=d.alias;b&&!d.pattern.global&&(c=d.pattern.toString().match(/[imsuy]*$/)[0],d.pattern=RegExp(d.pattern.source,c+"g"));for(var f=d.pattern||d,h=r.next,y=s;h!==t.tail&&!(i&&y>=i.reach);y+=h.value.length,h=h.next){var w=h.value;if(t.length>n.length)return;if(!(w instanceof T)){var k,v=1;if(b){if(!(k=B(f,y,n,g))||k.index>=n.length)break;var x=k.index,_=k.index+k[0].length,F=y;for(F+=h.value.length;F<=x;)F+=(h=h.next).value.length;if(y=F-=h.value.length,h.value instanceof T)continue;for(var S=h;S!==t.tail&&(F<_||"string"==typeof S.value);S=S.next)v++,F+=S.value.length;v--,w=n.slice(y,F),k.index-=y}else if(!(k=B(f,0,w,g)))continue;var x=k.index,A=k[0],P=w.slice(0,x),$=w.slice(x+A.length),z=y+w.length;i&&z>i.reach&&(i.reach=z);var E=h.prev;P&&(E=O(t,E,P),y+=P.length),function(e,n,t){for(var a=n.next,r=0;r<t&&a!==e.tail;r++)a=a.next;(n.next=a).prev=n,e.length-=r}(t,E,v);var C,h=O(t,E,new T(o,p?j.tokenize(A,p):A,m,A));$&&O(t,h,$),1<v&&(e(n,t,a,h.prev,y,C={cause:o+","+u,reach:z}),i&&C.reach>i.reach&&(i.reach=C.reach))}}}}(e,r,n,r.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(r)},hooks:{all:{},add:function(e,n){var t=j.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=j.hooks.all[e];if(t&&t.length)for(var a,r=0;a=t[r++];)a(n)}},Token:T};function T(e,n,t,a){this.type=e,this.content=n,this.alias=t,this.length=0|(a||"").length}function B(e,n,t,a){e.lastIndex=n;var r,s=e.exec(t);return s&&a&&s[1]&&(r=s[1].length,s.index+=r,s[0]=s[0].slice(r)),s}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function O(e,n,t){var a=n.next,r={value:t,prev:n,next:a};return n.next=r,a.prev=r,e.length++,r}if(u.Prism=j,T.stringify=function n(e,t){if("string"==typeof e)return e;if(Array.isArray(e)){var a="";return e.forEach(function(e){a+=n(e,t)}),a}var r={type:e.type,content:n(e.content,t),tag:"span",classes:["token",e.type],attributes:{},language:t},s=e.alias;s&&(Array.isArray(s)?Array.prototype.push.apply(r.classes,s):r.classes.push(s)),j.hooks.run("wrap",r);var i,o="";for(i in r.attributes)o+=" "+i+'="'+(r.attributes[i]||"").replace(/"/g,""")+'"';return"<"+r.tag+' class="'+r.classes.join(" ")+'"'+o+">"+r.content+"</"+r.tag+">"},!u.document)return u.addEventListener&&(j.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,a=n.code,r=n.immediateClose;u.postMessage(j.highlight(a,j.languages[t],t)),r&&u.close()},!1)),j;var a,r=j.util.currentScript();function i(){j.manual||j.highlightAll()}return r&&(j.filename=r.src,r.hasAttribute("data-manual")&&(j.manual=!0)),j.manual||("loading"===(a=document.readyState)||"interactive"===a&&r&&r.defer?document.addEventListener("DOMContentLoaded",i):window.requestAnimationFrame?window.requestAnimationFrame(i):window.setTimeout(i,16)),j}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{}),e.exports&&(e.exports=n),void 0!==u&&(u.Prism=n),Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},h=Prism,Object.defineProperties(h.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,r,e,s){var i;a.language===r&&(i=a.tokenStack=[],a.code=a.code.replace(e,function(e){if("function"==typeof s&&!s(e))return e;for(var n,t=i.length;-1!==a.code.indexOf(n=y(r,t));)++t;return i[t]=e,n}),a.grammar=h.languages.markup)}},tokenizePlaceholders:{value:function(g,b){var m,f;g.language===b&&g.tokenStack&&(g.grammar=h.languages[b],m=0,f=Object.keys(g.tokenStack),function e(n){for(var t=0;t<n.length&&!(m>=f.length);t++){var a,r,s,i,o,l,u,c,d,p=n[t];"string"==typeof p||p.content&&"string"==typeof p.content?(a=f[m],r=g.tokenStack[a],s="string"==typeof p?p:p.content,i=y(b,a),-1<(o=s.indexOf(i))&&(++m,l=s.substring(0,o),u=new h.Token(b,h.tokenize(r,g.grammar),"language-"+b,r),c=s.substring(o+i.length),d=[],l&&d.push.apply(d,e([l])),d.push(u),c&&d.push.apply(d,e([c])),"string"==typeof p?n.splice.apply(n,[t,1].concat(d)):p.content=d)):p.content&&e(p.content)}return n}(g.tokens))}}}),Prism.languages.c=Prism.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean,t=Prism,a=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,r=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,function(){return a.source}),t.languages.cpp=t.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,function(){return a.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:a,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),t.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,function(){return r})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),t.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t.languages.cpp}}}}),t.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),t.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:t.languages.extend("cpp",{})}}),t.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},t.languages.cpp["base-clause"]),function(t){function a(e,t){return e.replace(/<<(\d+)>>/g,function(e,n){return"(?:"+t[+n]+")"})}function r(e,n,t){return RegExp(a(e,n),t||"")}function e(e,n){for(var t=0;t<n;t++)e=e.replace(/<<self>>/g,function(){return"(?:"+e+")"});return e.replace(/<<self>>/g,"[^\\s\\S]")}var n="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",s="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",o="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var u=l(s),c=RegExp(l(n+" "+s+" "+i+" "+o)),d=l(s+" "+i+" "+o),p=l(n+" "+s+" "+o),g=e(/<(?:[^<>;=+\-*/%&|^]|<<self>>)*>/.source,2),b=e(/\((?:[^()]|<<self>>)*\)/.source,2),m=/@?\b[A-Za-z_]\w*\b/.source,f=a(/<<0>>(?:\s*<<1>>)?/.source,[m,g]),h=a(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[d,f]),y=/\[\s*(?:,\s*)*\]/.source,w=a(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[h,y]),k=a(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[g,b,y]),v=a(/\(<<0>>+(?:,<<0>>+)+\)/.source,[k]),x=a(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[v,h,y]),_={keyword:c,punctuation:/[<>()?,.:[\]]/},F=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,S=/"(?:\\.|[^\\"\r\n])*"/.source;t.languages.csharp=t.languages.extend("clike",{string:[{pattern:r(/(^|[^$\\])<<0>>/.source,[/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source]),lookbehind:!0,greedy:!0},{pattern:r(/(^|[^@$\\])<<0>>/.source,[S]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:r(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[h]),lookbehind:!0,inside:_},{pattern:r(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[m,x]),lookbehind:!0,inside:_},{pattern:r(/(\busing\s+)<<0>>(?=\s*=)/.source,[m]),lookbehind:!0},{pattern:r(/(\b<<0>>\s+)<<1>>/.source,[u,f]),lookbehind:!0,inside:_},{pattern:r(/(\bcatch\s*\(\s*)<<0>>/.source,[h]),lookbehind:!0,inside:_},{pattern:r(/(\bwhere\s+)<<0>>/.source,[m]),lookbehind:!0},{pattern:r(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[w]),lookbehind:!0,inside:_},{pattern:r(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[x,p,m]),inside:_}],keyword:c,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),t.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),t.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:r(/([(,]\s*)<<0>>(?=\s*:)/.source,[m]),lookbehind:!0,alias:"punctuation"}}),t.languages.insertBefore("csharp","class-name",{namespace:{pattern:r(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[m]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:r(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[b]),lookbehind:!0,alias:"class-name",inside:_},"return-type":{pattern:r(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[x,h]),inside:_,alias:"class-name"},"constructor-invocation":{pattern:r(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[x]),lookbehind:!0,inside:_,alias:"class-name"},"generic-method":{pattern:r(/<<0>>\s*<<1>>(?=\s*\()/.source,[m,g]),inside:{function:r(/^<<0>>/.source,[m]),generic:{pattern:RegExp(g),alias:"class-name",inside:_}}},"type-list":{pattern:r(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[u,f,m,x,c.source,b,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:r(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[f,b]),lookbehind:!0,greedy:!0,inside:t.languages.csharp},keyword:c,"class-name":{pattern:RegExp(x),greedy:!0,inside:_},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var A=S+"|"+F,P=a(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[A]),$=e(a(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[P]),2),z=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,E=a(/<<0>>(?:\s*\(<<1>>*\))?/.source,[h,$]);t.languages.insertBefore("csharp","class-name",{attribute:{pattern:r(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[z,E]),lookbehind:!0,greedy:!0,inside:{target:{pattern:r(/^<<0>>(?=\s*:)/.source,[z]),alias:"keyword"},"attribute-arguments":{pattern:r(/\(<<0>>*\)/.source,[$]),inside:t.languages.csharp},"class-name":{pattern:RegExp(h),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var C=/:[^}\r\n]+/.source,j=e(a(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[P]),2),T=a(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[j,C]),B=e(a(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<<self>>*\)/.source,[A]),2),O=a(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[B,C]);function R(e,n){return{interpolation:{pattern:r(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[e]),lookbehind:!0,inside:{"format-string":{pattern:r(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[n,C]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:t.languages.csharp}}},string:/[\s\S]+/}}t.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:r(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[T]),lookbehind:!0,greedy:!0,inside:R(T,j)},{pattern:r(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[O]),lookbehind:!0,greedy:!0,inside:R(O,B)}],char:{pattern:RegExp(F),greedy:!0}}),t.languages.dotnet=t.languages.cs=t.languages.csharp}(Prism),function(e){var n=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+n.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+n.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+n.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+n.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:n,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var t=e.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism),s=Prism,i=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,o=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,l={pattern:RegExp(/(^|[^\w.])/.source+o+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}},s.languages.java=s.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[l,{pattern:RegExp(/(^|[^\w.])/.source+o+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:l.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+o+/[A-Z]\w*\b/.source),lookbehind:!0,inside:l.inside}],keyword:i,function:[s.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),s.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),s.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":l,keyword:i,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+o+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:l.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+o+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:l.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!<keyword>)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(/<keyword>/g,function(){return i.source})),lookbehind:!0,inside:{punctuation:/\./}}}),Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),Prism.languages.js=Prism.languages.javascript,Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,n){var t={};t["language-"+n]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[n]},t.cdata=/^<!\[CDATA\[|\]\]>$/i;var a={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:t}};a["language-"+n]={pattern:/[\s\S]+/,inside:Prism.languages[n]};var r={};r[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:a},Prism.languages.insertBefore("markup","cdata",r)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,n){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[n,"language-"+n],inside:Prism.languages[n]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml,function(n){var e=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,t=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],a=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,r=/<?=>|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,s=/[{}\[\](),:;]/;n.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:e,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:a,operator:r,punctuation:s};var i={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:n.languages.php},o=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:i}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:i}}];n.languages.insertBefore("php","variable",{string:o,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:e,string:o,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,number:a,operator:r,punctuation:s}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),n.hooks.add("before-tokenize",function(e){/<\?/.test(e.code)&&n.languages["markup-templating"].buildPlaceholders(e,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)}),n.hooks.add("after-tokenize",function(e){n.languages["markup-templating"].tokenizePlaceholders(e,"php")})}(Prism),Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python,function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===|<?=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var n={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var t="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",a=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+t+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+a),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+a+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+t),greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+t),greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism),{boltExport:c.exports}},"object"==typeof e&&void 0!==n?n.exports=a():(t="undefined"!=typeof globalThis?globalThis:t||self).EphoxContactWrapper=a();window.Prism;window.Prism=r}(b,m);var k=m.exports.boltExport;t.add("codesample",function(n){var t,r,a,s;function e(){return s.execCommand("codesample")}r=(t=n).$,t.on("PreProcess",function(e){r("pre[contenteditable=false]",e.node).filter(c(u)).each(function(e,n){var t=r(n),a=n.textContent;t.attr("class",r.trim(t.attr("class"))),t.removeAttr("contentEditable"),t.empty().append(r("<code></code>").each(function(){this.textContent=a}))})}),t.on("SetContent",function(){var e=r("pre").filter(c(u)).filter(function(e,n){return"false"!==n.contentEditable});e.length&&t.undoManager.transact(function(){e.each(function(e,n){r(n).find("br").each(function(e,n){n.parentNode.replaceChild(t.getDoc().createTextNode("\n"),n)}),n.contentEditable="false",n.innerHTML=t.dom.encode(n.textContent),h(t).highlightElement(n),n.className=r.trim(n.className)})})}),(s=n).ui.registry.addToggleButton("codesample",{icon:"code-sample",tooltip:"Insert/edit code sample",onAction:e,onSetup:function(t){function e(){var e,n;t.setActive((n=(e=s).selection.getStart(),e.dom.is(n,'pre[class*="language-"]')))}return s.on("NodeChange",e),function(){return s.off("NodeChange",e)}}}),s.ui.registry.addMenuItem("codesample",{text:"Code sample...",icon:"code-sample",onAction:e}),(a=n).addCommand("codesample",function(){var e=a.selection.getNode();a.selection.isCollapsed()||u(e)?w(a):a.formatter.toggle("code")}),n.on("dblclick",function(e){u(e.target)&&w(n)})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/colorpicker/plugin.min.js b/apps/st/public/tinymce/plugins/colorpicker/plugin.min.js new file mode 100644 index 000000000..70ba37be0 --- /dev/null +++ b/apps/st/public/tinymce/plugins/colorpicker/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("colorpicker",function(){})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/contextmenu/plugin.min.js b/apps/st/public/tinymce/plugins/contextmenu/plugin.min.js new file mode 100644 index 000000000..e4bb1d96c --- /dev/null +++ b/apps/st/public/tinymce/plugins/contextmenu/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("contextmenu",function(){})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/directionality/plugin.min.js b/apps/st/public/tinymce/plugins/directionality/plugin.min.js new file mode 100644 index 000000000..dabea70e7 --- /dev/null +++ b/apps/st/public/tinymce/plugins/directionality/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function n(t){return function(n){return typeof n===t}}function u(n){return function(){return n}}function t(n){return n}function r(){return d}var o,e=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=function(n){return r=typeof(t=n),(null===t?"null":"object"==r&&(Array.prototype.isPrototypeOf(t)||t.constructor&&"Array"===t.constructor.name)?"array":"object"==r&&(String.prototype.isPrototypeOf(t)||t.constructor&&"String"===t.constructor.name)?"string":r)===o;var t,r},c=n("boolean"),f=n("function"),l=n("number"),a=u(!(o="string")),m=u(!0),d={fold:function(n,t){return n()},isSome:a,isNone:m,getOr:t,getOrThunk:s,getOrDie:function(n){throw new Error(n||"error: getOrDie called on none.")},getOrNull:u(null),getOrUndefined:u(void 0),or:t,orThunk:s,map:r,each:function(){},bind:r,exists:a,forall:m,filter:function(){return d},toArray:function(){return[]},toString:u("none()")};function s(n){return n()}function g(n,t){for(var r=0,o=n.length;r<o;r++)t(n[r],r)}function h(n,t){var r=n.dom;if(1!==r.nodeType)return!1;var o=r;if(void 0!==o.matches)return o.matches(t);if(void 0!==o.msMatchesSelector)return o.msMatchesSelector(t);if(void 0!==o.webkitMatchesSelector)return o.webkitMatchesSelector(t);if(void 0!==o.mozMatchesSelector)return o.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}function v(n){if(null==n)throw new Error("Node cannot be null or undefined");return{dom:n}}var p=function(r){function n(){return e}function t(n){return n(r)}var o=u(r),e={fold:function(n,t){return t(r)},isSome:m,isNone:a,getOr:o,getOrThunk:o,getOrDie:o,getOrNull:o,getOrUndefined:o,or:n,orThunk:n,map:function(n){return p(n(r))},each:function(n){n(r)},bind:t,exists:t,forall:t,filter:function(n){return n(r)?e:d},toArray:function(){return[r]},toString:function(){return"some("+r+")"}};return e},y={some:p,none:r,from:function(n){return null==n?d:p(n)}},w={fromHtml:function(n,t){var r=(t||document).createElement("div");if(r.innerHTML=n,!r.hasChildNodes()||1<r.childNodes.length)throw console.error("HTML does not have a single root node",n),new Error("HTML must have a single root node");return v(r.childNodes[0])},fromTag:function(n,t){var r=(t||document).createElement(n);return v(r)},fromText:function(n,t){var r=(t||document).createTextNode(n);return v(r)},fromDom:v,fromPoint:function(n,t,r){return y.from(n.dom.elementFromPoint(t,r)).map(v)}};function D(t){return function(n){return n.dom.nodeType===t}}function N(n,t,r){!function(n,t,r){if(!(i(r)||c(r)||l(r)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",r,":: Element ",n),new Error("Attribute value was not simple");n.setAttribute(t,r+"")}(n.dom,t,r)}function T(n,t){n.dom.removeAttribute(t)}function S(n){return w.fromDom(n.dom.host)}function b(e,u,n){return function(n){for(var t=e.dom,r=f(n)?n:a;t.parentNode;){var t=t.parentNode,o=w.fromDom(t);if(h(o,u))return y.some(o);if(r(o))break}return y.none()}(n)}function O(n){return"rtl"===(r="direction",o=(t=n).dom,""!==(e=window.getComputedStyle(o).getPropertyValue(r))||B(t)?e:H(o,r))?"rtl":"ltr";var t,r,o,e}function A(n,t){return r=function(n){return h(n,t)},function(n,t){for(var r=[],o=0,e=n.length;o<e;o++){var u=n[o];t(u,o)&&r.push(u)}return r}(function(n,t){for(var r=n.length,o=new Array(r),e=0;e<r;e++){var u=n[e];o[e]=t(u,e)}return o}(n.dom.childNodes,w.fromDom),r);var r}function C(n,u){var t=n.selection.getSelectedBlocks();0<t.length&&(g(t,function(n){var t,r=w.fromDom(n),o=k(r),e=(t=r,(o?b(t,"ol,ul"):y.some(t)).getOr(t));y.from(e.dom.parentNode).map(w.fromDom).filter(E).each(function(n){O(n)!==u?N(e,"dir",u):O(e)!==u&&T(e,"dir"),o&&g(A(e,"li[dir]"),function(n){return T(n,"dir"),0})})}),n.nodeChanged())}function M(t,o){return function(r){function n(n){var t=w.fromDom(n.element);r.setActive(O(t)===o)}return t.on("NodeChange",n),function(){return t.off("NodeChange",n)}}}"undefined"!=typeof window||Function("return this;")();function k(n){return E(n)&&"li"===n.dom.nodeName.toLowerCase()}var E=D(1),L=D(3),P=D(9),R=D(11),x=f(Element.prototype.attachShadow)&&f(Node.prototype.getRootNode)?function(n){return w.fromDom(n.dom.getRootNode())}:function(n){return P(n)?n:w.fromDom(n.dom.ownerDocument)},B=function(n){var t=L(n)?n.dom.parentNode:n.dom;if(null==t||null===t.ownerDocument)return!1;var r,o,e,u,i,c=t.ownerDocument;return e=w.fromDom(t),i=x(e),(R(u=i)&&null!=u.dom.host?y.some(i):y.none()).fold(function(){return c.body.contains(t)},(r=B,o=S,function(n){return r(o(n))}))},H=function(n,t){return void 0!==n.style&&f(n.style.getPropertyValue)?n.style.getPropertyValue(t):""};e.add("directionality",function(n){var t,r;(t=n).addCommand("mceDirectionLTR",function(){C(t,"ltr")}),t.addCommand("mceDirectionRTL",function(){C(t,"rtl")}),(r=n).ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:function(){return r.execCommand("mceDirectionLTR")},onSetup:M(r,"ltr")}),r.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:function(){return r.execCommand("mceDirectionRTL")},onSetup:M(r,"rtl")})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.js b/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.js new file mode 100644 index 000000000..d5f9734ba --- /dev/null +++ b/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.js @@ -0,0 +1,9424 @@ +// Source: npm package: emojilib +// Images provided by twemoji: https://github.com/twitter/twemoji +window.tinymce.Resource.add("tinymce.plugins.emoticons", { + 100: { + keywords: [ "score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcaf" src="1f4af.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + 1234: { + keywords: [ "numbers", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd22" src="1f522.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + grinning: { + keywords: [ "face", "smile", "happy", "joy", ":D", "grin" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude00" src="1f600.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + grimacing: { + keywords: [ "face", "grimace", "teeth" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2c" src="1f62c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + grin: { + keywords: [ "face", "happy", "smile", "joy", "kawaii" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude01" src="1f601.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + joy: { + keywords: [ "face", "cry", "tears", "weep", "happy", "happytears", "haha" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude02" src="1f602.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + rofl: { + keywords: [ "face", "rolling", "floor", "laughing", "lol", "haha" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd23" src="1f923.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + partying: { + keywords: [ "face", "celebration", "woohoo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd73" src="1f973.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smiley: { + keywords: [ "face", "happy", "joy", "haha", ":D", ":)", "smile", "funny" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude03" src="1f603.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smile: { + keywords: [ "face", "happy", "joy", "funny", "haha", "laugh", "like", ":D", ":)" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude04" src="1f604.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sweat_smile: { + keywords: [ "face", "hot", "happy", "laugh", "sweat", "smile", "relief" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude05" src="1f605.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + laughing: { + keywords: [ "happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude06" src="1f606.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + innocent: { + keywords: [ "face", "angel", "heaven", "halo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude07" src="1f607.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + wink: { + keywords: [ "face", "happy", "mischievous", "secret", ";)", "smile", "eye" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude09" src="1f609.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + blush: { + keywords: [ "face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0a" src="1f60a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + slightly_smiling_face: { + keywords: [ "face", "smile" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude42" src="1f642.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + upside_down_face: { + keywords: [ "face", "flipped", "silly", "smile" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude43" src="1f643.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + relaxed: { + keywords: [ "face", "blush", "massage", "happiness" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u263a\ufe0f" src="263a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + yum: { + keywords: [ "happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0b" src="1f60b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + relieved: { + keywords: [ "face", "relaxed", "phew", "massage", "happiness" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0c" src="1f60c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + heart_eyes: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0d" src="1f60d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smiling_face_with_three_hearts: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd70" src="1f970.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kissing_heart: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude18" src="1f618.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kissing: { + keywords: [ "love", "like", "face", "3", "valentines", "infatuation", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude17" src="1f617.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kissing_smiling_eyes: { + keywords: [ "face", "affection", "valentines", "infatuation", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude19" src="1f619.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kissing_closed_eyes: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1a" src="1f61a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue_winking_eye: { + keywords: [ "face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1c" src="1f61c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + zany: { + keywords: [ "face", "goofy", "crazy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2a" src="1f92a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + raised_eyebrow: { + keywords: [ "face", "distrust", "scepticism", "disapproval", "disbelief", "surprise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd28" src="1f928.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + monocle: { + keywords: [ "face", "stuffy", "wealthy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd0" src="1f9d0.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue_closed_eyes: { + keywords: [ "face", "prank", "playful", "mischievous", "smile", "tongue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1d" src="1f61d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue: { + keywords: [ "face", "prank", "childish", "playful", "mischievous", "smile", "tongue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1b" src="1f61b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + money_mouth_face: { + keywords: [ "face", "rich", "dollar", "money" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd11" src="1f911.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + nerd_face: { + keywords: [ "face", "nerdy", "geek", "dork" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd13" src="1f913.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sunglasses: { + keywords: [ "face", "cool", "smile", "summer", "beach", "sunglass" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0e" src="1f60e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + star_struck: { + keywords: [ "face", "smile", "starry", "eyes", "grinning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd29" src="1f929.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + clown_face: { + keywords: [ "face" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd21" src="1f921.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + cowboy_hat_face: { + keywords: [ "face", "cowgirl", "hat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd20" src="1f920.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + hugs: { + keywords: [ "face", "smile", "hug" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd17" src="1f917.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smirk: { + keywords: [ "face", "smile", "mean", "prank", "smug", "sarcasm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0f" src="1f60f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + no_mouth: { + keywords: [ "face", "hellokitty" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude36" src="1f636.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + neutral_face: { + keywords: [ "indifference", "meh", ":|", "neutral" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude10" src="1f610.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + expressionless: { + keywords: [ "face", "indifferent", "-_-", "meh", "deadpan" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude11" src="1f611.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + unamused: { + keywords: [ "indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude12" src="1f612.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + roll_eyes: { + keywords: [ "face", "eyeroll", "frustrated" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude44" src="1f644.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + thinking: { + keywords: [ "face", "hmmm", "think", "consider" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd14" src="1f914.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + lying_face: { + keywords: [ "face", "lie", "pinocchio" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd25" src="1f925.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + hand_over_mouth: { + keywords: [ "face", "whoops", "shock", "surprise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2d" src="1f92d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + shushing: { + keywords: [ "face", "quiet", "shhh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2b" src="1f92b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + symbols_over_mouth: { + keywords: [ "face", "swearing", "cursing", "cussing", "profanity", "expletive" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2c" src="1f92c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + exploding_head: { + keywords: [ "face", "shocked", "mind", "blown" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2f" src="1f92f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + flushed: { + keywords: [ "face", "blush", "shy", "flattered" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude33" src="1f633.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + disappointed: { + keywords: [ "face", "sad", "upset", "depressed", ":(" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1e" src="1f61e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + worried: { + keywords: [ "face", "concern", "nervous", ":(" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1f" src="1f61f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + angry: { + keywords: [ "mad", "face", "annoyed", "frustrated" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude20" src="1f620.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + rage: { + keywords: [ "angry", "mad", "hate", "despise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude21" src="1f621.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + pensive: { + keywords: [ "face", "sad", "depressed", "upset" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude14" src="1f614.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + confused: { + keywords: [ "face", "indifference", "huh", "weird", "hmmm", ":/" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude15" src="1f615.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + slightly_frowning_face: { + keywords: [ "face", "frowning", "disappointed", "sad", "upset" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude41" src="1f641.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + frowning_face: { + keywords: [ "face", "sad", "upset", "frown" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2639" src="2639.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + persevere: { + keywords: [ "face", "sick", "no", "upset", "oops" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude23" src="1f623.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + confounded: { + keywords: [ "face", "confused", "sick", "unwell", "oops", ":S" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude16" src="1f616.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + tired_face: { + keywords: [ "sick", "whine", "upset", "frustrated" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2b" src="1f62b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + weary: { + keywords: [ "face", "tired", "sleepy", "sad", "frustrated", "upset" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude29" src="1f629.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + pleading: { + keywords: [ "face", "begging", "mercy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7a" src="1f97a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + triumph: { + keywords: [ "face", "gas", "phew", "proud", "pride" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude24" src="1f624.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + open_mouth: { + keywords: [ "face", "surprise", "impressed", "wow", "whoa", ":O" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2e" src="1f62e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + scream: { + keywords: [ "face", "munch", "scared", "omg" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude31" src="1f631.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + fearful: { + keywords: [ "face", "scared", "terrified", "nervous", "oops", "huh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude28" src="1f628.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + cold_sweat: { + keywords: [ "face", "nervous", "sweat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude30" src="1f630.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + hushed: { + keywords: [ "face", "woo", "shh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2f" src="1f62f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + frowning: { + keywords: [ "face", "aw", "what" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude26" src="1f626.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + anguished: { + keywords: [ "face", "stunned", "nervous" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude27" src="1f627.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + cry: { + keywords: [ "face", "tears", "sad", "depressed", "upset", ":'(" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude22" src="1f622.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + disappointed_relieved: { + keywords: [ "face", "phew", "sweat", "nervous" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude25" src="1f625.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + drooling_face: { + keywords: [ "face" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd24" src="1f924.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sleepy: { + keywords: [ "face", "tired", "rest", "nap" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2a" src="1f62a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sweat: { + keywords: [ "face", "hot", "sad", "tired", "exercise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude13" src="1f613.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + hot: { + keywords: [ "face", "feverish", "heat", "red", "sweating" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd75" src="1f975.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + cold: { + keywords: [ "face", "blue", "freezing", "frozen", "frostbite", "icicles" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd76" src="1f976.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sob: { + keywords: [ "face", "cry", "tears", "sad", "upset", "depressed" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2d" src="1f62d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + dizzy_face: { + keywords: [ "spent", "unconscious", "xox", "dizzy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude35" src="1f635.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + astonished: { + keywords: [ "face", "xox", "surprised", "poisoned" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude32" src="1f632.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + zipper_mouth_face: { + keywords: [ "face", "sealed", "zipper", "secret" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd10" src="1f910.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + nauseated_face: { + keywords: [ "face", "vomit", "gross", "green", "sick", "throw up", "ill" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd22" src="1f922.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sneezing_face: { + keywords: [ "face", "gesundheit", "sneeze", "sick", "allergy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd27" src="1f927.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + vomiting: { + keywords: [ "face", "sick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2e" src="1f92e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + mask: { + keywords: [ "face", "sick", "ill", "disease" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude37" src="1f637.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + face_with_thermometer: { + keywords: [ "sick", "temperature", "thermometer", "cold", "fever" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd12" src="1f912.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + face_with_head_bandage: { + keywords: [ "injured", "clumsy", "bandage", "hurt" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd15" src="1f915.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + woozy: { + keywords: [ "face", "dizzy", "intoxicated", "tipsy", "wavy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd74" src="1f974.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sleeping: { + keywords: [ "face", "tired", "sleepy", "night", "zzz" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude34" src="1f634.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + zzz: { + keywords: [ "sleepy", "tired", "dream" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca4" src="1f4a4.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + poop: { + keywords: [ "hankey", "shitface", "fail", "turd", "shit" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca9" src="1f4a9.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smiling_imp: { + keywords: [ "devil", "horns" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude08" src="1f608.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + imp: { + keywords: [ "devil", "angry", "horns" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7f" src="1f47f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + japanese_ogre: { + keywords: [ "monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc79" src="1f479.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + japanese_goblin: { + keywords: [ "red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7a" src="1f47a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + skull: { + keywords: [ "dead", "skeleton", "creepy", "death" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc80" src="1f480.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + ghost: { + keywords: [ "halloween", "spooky", "scary" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7b" src="1f47b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + alien: { + keywords: [ "UFO", "paul", "weird", "outer_space" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7d" src="1f47d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + robot: { + keywords: [ "computer", "machine", "bot" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd16" src="1f916.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smiley_cat: { + keywords: [ "animal", "cats", "happy", "smile" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3a" src="1f63a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smile_cat: { + keywords: [ "animal", "cats", "smile" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude38" src="1f638.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + joy_cat: { + keywords: [ "animal", "cats", "haha", "happy", "tears" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude39" src="1f639.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + heart_eyes_cat: { + keywords: [ "animal", "love", "like", "affection", "cats", "valentines", "heart" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3b" src="1f63b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + smirk_cat: { + keywords: [ "animal", "cats", "smirk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3c" src="1f63c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kissing_cat: { + keywords: [ "animal", "cats", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3d" src="1f63d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + scream_cat: { + keywords: [ "animal", "cats", "munch", "scared", "scream" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude40" src="1f640.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + crying_cat_face: { + keywords: [ "animal", "tears", "weep", "sad", "cats", "upset", "cry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3f" src="1f63f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + pouting_cat: { + keywords: [ "animal", "cats" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3e" src="1f63e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + palms_up: { + keywords: [ "hands", "gesture", "cupped", "prayer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd32" src="1f932.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raised_hands: { + keywords: [ "gesture", "hooray", "yea", "celebration", "hands" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4c" src="1f64c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + clap: { + keywords: [ "hands", "praise", "applause", "congrats", "yay" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4f" src="1f44f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + wave: { + keywords: [ "hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4b" src="1f44b.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + call_me_hand: { + keywords: [ "hands", "gesture" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd19" src="1f919.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + "+1": { + keywords: [ "thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4d" src="1f44d.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + "-1": { + keywords: [ "thumbsdown", "no", "dislike", "hand" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4e" src="1f44e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + facepunch: { + keywords: [ "angry", "violence", "fist", "hit", "attack", "hand" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4a" src="1f44a.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + fist: { + keywords: [ "fingers", "hand", "grasp" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270a" src="270a.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + fist_left: { + keywords: [ "hand", "fistbump" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1b" src="1f91b.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + fist_right: { + keywords: [ "hand", "fistbump" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1c" src="1f91c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + v: { + keywords: [ "fingers", "ohyeah", "hand", "peace", "victory", "two" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270c" src="270c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + ok_hand: { + keywords: [ "fingers", "limbs", "perfect", "ok", "okay" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4c" src="1f44c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raised_hand: { + keywords: [ "fingers", "stop", "highfive", "palm", "ban" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270b" src="270b.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raised_back_of_hand: { + keywords: [ "fingers", "raised", "backhand" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1a" src="1f91a.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + open_hands: { + keywords: [ "fingers", "butterfly", "hands", "open" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc50" src="1f450.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + muscle: { + keywords: [ "arm", "flex", "hand", "summer", "strong", "biceps" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcaa" src="1f4aa.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + pray: { + keywords: [ "please", "hope", "wish", "namaste", "highfive" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4f" src="1f64f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + foot: { + keywords: [ "kick", "stomp" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb6" src="1f9b6.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + leg: { + keywords: [ "kick", "limb" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb5" src="1f9b5.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + handshake: { + keywords: [ "agreement", "shake" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1d" src="1f91d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + point_up: { + keywords: [ "hand", "fingers", "direction", "up" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u261d" src="261d.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + point_up_2: { + keywords: [ "fingers", "hand", "direction", "up" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc46" src="1f446.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + point_down: { + keywords: [ "fingers", "hand", "direction", "down" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc47" src="1f447.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + point_left: { + keywords: [ "direction", "fingers", "hand", "left" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc48" src="1f448.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + point_right: { + keywords: [ "fingers", "hand", "direction", "right" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc49" src="1f449.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + fu: { + keywords: [ "hand", "fingers", "rude", "middle", "flipping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd95" src="1f595.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raised_hand_with_fingers_splayed: { + keywords: [ "hand", "fingers", "palm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd90" src="1f590.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + love_you: { + keywords: [ "hand", "fingers", "gesture" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1f" src="1f91f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + metal: { + keywords: [ "hand", "fingers", "evil_eye", "sign_of_horns", "rock_on" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd18" src="1f918.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + crossed_fingers: { + keywords: [ "good", "lucky" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1e" src="1f91e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + vulcan_salute: { + keywords: [ "hand", "fingers", "spock", "star trek" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd96" src="1f596.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + writing_hand: { + keywords: [ "lower_left_ballpoint_pen", "stationery", "write", "compose" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270d" src="270d.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + selfie: { + keywords: [ "camera", "phone" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd33" src="1f933.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + nail_care: { + keywords: [ "beauty", "manicure", "finger", "fashion", "nail" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc85" src="1f485.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + lips: { + keywords: [ "mouth", "kiss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc44" src="1f444.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + tooth: { + keywords: [ "teeth", "dentist" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb7" src="1f9b7.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + tongue: { + keywords: [ "mouth", "playful" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc45" src="1f445.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + ear: { + keywords: [ "face", "hear", "sound", "listen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc42" src="1f442.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + nose: { + keywords: [ "smell", "sniff" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc43" src="1f443.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + eye: { + keywords: [ "face", "look", "see", "watch", "stare" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc41" src="1f441.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + eyes: { + keywords: [ "look", "watch", "stalk", "peek", "see" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc40" src="1f440.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + brain: { + keywords: [ "smart", "intelligent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde0" src="1f9e0.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + bust_in_silhouette: { + keywords: [ "user", "person", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc64" src="1f464.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + busts_in_silhouette: { + keywords: [ "user", "person", "human", "group", "team" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc65" src="1f465.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + speaking_head: { + keywords: [ "user", "person", "human", "sing", "say", "talk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde3" src="1f5e3.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + baby: { + keywords: [ "child", "boy", "girl", "toddler" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc76" src="1f476.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + child: { + keywords: [ "gender-neutral", "young" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd2" src="1f9d2.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + boy: { + keywords: [ "man", "male", "guy", "teenager" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc66" src="1f466.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + girl: { + keywords: [ "female", "woman", "teenager" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc67" src="1f467.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + adult: { + keywords: [ "gender-neutral", "person" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd1" src="1f9d1.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man: { + keywords: [ "mustache", "father", "dad", "guy", "classy", "sir", "moustache" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68" src="1f468.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman: { + keywords: [ "female", "girls", "lady" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69" src="1f469.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + blonde_woman: { + keywords: [ "woman", "female", "girl", "blonde", "person" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc71\u200d\u2640\ufe0f" src="1f471-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + blonde_man: { + keywords: [ "man", "male", "boy", "blonde", "guy", "person" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc71" src="1f471.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + bearded_person: { + keywords: [ "person", "bewhiskered" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd4" src="1f9d4.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + older_adult: { + keywords: [ "human", "elder", "senior", "gender-neutral" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd3" src="1f9d3.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + older_man: { + keywords: [ "human", "male", "men", "old", "elder", "senior" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc74" src="1f474.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + older_woman: { + keywords: [ "human", "female", "women", "lady", "old", "elder", "senior" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc75" src="1f475.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_with_gua_pi_mao: { + keywords: [ "male", "boy", "chinese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc72" src="1f472.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_with_headscarf: { + keywords: [ "female", "hijab", "mantilla", "tichel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd5" src="1f9d5.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_with_turban: { + keywords: [ "female", "indian", "hinduism", "arabs", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc73\u200d\u2640\ufe0f" src="1f473-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_with_turban: { + keywords: [ "male", "indian", "hinduism", "arabs" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc73" src="1f473.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + policewoman: { + keywords: [ "woman", "police", "law", "legal", "enforcement", "arrest", "911", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6e\u200d\u2640\ufe0f" src="1f46e-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + policeman: { + keywords: [ "man", "police", "law", "legal", "enforcement", "arrest", "911" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6e" src="1f46e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + construction_worker_woman: { + keywords: [ "female", "human", "wip", "build", "construction", "worker", "labor", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc77\u200d\u2640\ufe0f" src="1f477-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + construction_worker_man: { + keywords: [ "male", "human", "wip", "guy", "build", "construction", "worker", "labor" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc77" src="1f477.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + guardswoman: { + keywords: [ "uk", "gb", "british", "female", "royal", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc82\u200d\u2640\ufe0f" src="1f482-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + guardsman: { + keywords: [ "uk", "gb", "british", "male", "guy", "royal" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc82" src="1f482.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + female_detective: { + keywords: [ "human", "spy", "detective", "female", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd75\ufe0f\u200d\u2640\ufe0f" src="1f575-fe0f-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + male_detective: { + keywords: [ "human", "spy", "detective" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd75" src="1f575.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_health_worker: { + keywords: [ "doctor", "nurse", "therapist", "healthcare", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2695\ufe0f" src="1f469-200d-2695-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_health_worker: { + keywords: [ "doctor", "nurse", "therapist", "healthcare", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2695\ufe0f" src="1f468-200d-2695-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_farmer: { + keywords: [ "rancher", "gardener", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf3e" src="1f469-200d-1f33e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_farmer: { + keywords: [ "rancher", "gardener", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf3e" src="1f468-200d-1f33e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_cook: { + keywords: [ "chef", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf73" src="1f469-200d-1f373.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_cook: { + keywords: [ "chef", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf73" src="1f468-200d-1f373.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_student: { + keywords: [ "graduate", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf93" src="1f469-200d-1f393.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_student: { + keywords: [ "graduate", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf93" src="1f468-200d-1f393.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_singer: { + keywords: [ "rockstar", "entertainer", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfa4" src="1f469-200d-1f3a4.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_singer: { + keywords: [ "rockstar", "entertainer", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfa4" src="1f468-200d-1f3a4.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_teacher: { + keywords: [ "instructor", "professor", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfeb" src="1f469-200d-1f3eb.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_teacher: { + keywords: [ "instructor", "professor", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfeb" src="1f468-200d-1f3eb.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_factory_worker: { + keywords: [ "assembly", "industrial", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfed" src="1f469-200d-1f3ed.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_factory_worker: { + keywords: [ "assembly", "industrial", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfed" src="1f468-200d-1f3ed.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_technologist: { + keywords: [ "coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udcbb" src="1f469-200d-1f4bb.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_technologist: { + keywords: [ "coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udcbb" src="1f468-200d-1f4bb.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_office_worker: { + keywords: [ "business", "manager", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udcbc" src="1f469-200d-1f4bc.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_office_worker: { + keywords: [ "business", "manager", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udcbc" src="1f468-200d-1f4bc.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_mechanic: { + keywords: [ "plumber", "woman", "human", "wrench" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udd27" src="1f469-200d-1f527.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_mechanic: { + keywords: [ "plumber", "man", "human", "wrench" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udd27" src="1f468-200d-1f527.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_scientist: { + keywords: [ "biologist", "chemist", "engineer", "physicist", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udd2c" src="1f469-200d-1f52c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_scientist: { + keywords: [ "biologist", "chemist", "engineer", "physicist", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udd2c" src="1f468-200d-1f52c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_artist: { + keywords: [ "painter", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfa8" src="1f469-200d-1f3a8.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_artist: { + keywords: [ "painter", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfa8" src="1f468-200d-1f3a8.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_firefighter: { + keywords: [ "fireman", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\ude92" src="1f469-200d-1f692.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_firefighter: { + keywords: [ "fireman", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\ude92" src="1f468-200d-1f692.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_pilot: { + keywords: [ "aviator", "plane", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2708\ufe0f" src="1f469-200d-2708-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_pilot: { + keywords: [ "aviator", "plane", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2708\ufe0f" src="1f468-200d-2708-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_astronaut: { + keywords: [ "space", "rocket", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\ude80" src="1f469-200d-1f680.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_astronaut: { + keywords: [ "space", "rocket", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\ude80" src="1f468-200d-1f680.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_judge: { + keywords: [ "justice", "court", "woman", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2696\ufe0f" src="1f469-200d-2696-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_judge: { + keywords: [ "justice", "court", "man", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2696\ufe0f" src="1f468-200d-2696-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_superhero: { + keywords: [ "woman", "female", "good", "heroine", "superpowers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb8\u200d\u2640\ufe0f" src="1f9b8-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_superhero: { + keywords: [ "man", "male", "good", "hero", "superpowers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb8\u200d\u2642\ufe0f" src="1f9b8-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_supervillain: { + keywords: [ "woman", "female", "evil", "bad", "criminal", "heroine", "superpowers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb9\u200d\u2640\ufe0f" src="1f9b9-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_supervillain: { + keywords: [ "man", "male", "evil", "bad", "criminal", "hero", "superpowers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb9\u200d\u2642\ufe0f" src="1f9b9-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + mrs_claus: { + keywords: [ "woman", "female", "xmas", "mother christmas" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd36" src="1f936.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + santa: { + keywords: [ "festival", "man", "male", "xmas", "father christmas" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf85" src="1f385.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + sorceress: { + keywords: [ "woman", "female", "mage", "witch" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd9\u200d\u2640\ufe0f" src="1f9d9-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + wizard: { + keywords: [ "man", "male", "mage", "sorcerer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd9\u200d\u2642\ufe0f" src="1f9d9-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_elf: { + keywords: [ "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddd\u200d\u2640\ufe0f" src="1f9dd-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_elf: { + keywords: [ "man", "male" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddd\u200d\u2642\ufe0f" src="1f9dd-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_vampire: { + keywords: [ "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddb\u200d\u2640\ufe0f" src="1f9db-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_vampire: { + keywords: [ "man", "male", "dracula" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddb\u200d\u2642\ufe0f" src="1f9db-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_zombie: { + keywords: [ "woman", "female", "undead", "walking dead" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddf\u200d\u2640\ufe0f" src="1f9df-200d-2640-fe0f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + man_zombie: { + keywords: [ "man", "male", "dracula", "undead", "walking dead" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddf\u200d\u2642\ufe0f" src="1f9df-200d-2642-fe0f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + woman_genie: { + keywords: [ "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddde\u200d\u2640\ufe0f" src="1f9de-200d-2640-fe0f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + man_genie: { + keywords: [ "man", "male" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddde\u200d\u2642\ufe0f" src="1f9de-200d-2642-fe0f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + mermaid: { + keywords: [ "woman", "female", "merwoman", "ariel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddc\u200d\u2640\ufe0f" src="1f9dc-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + merman: { + keywords: [ "man", "male", "triton" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddc\u200d\u2642\ufe0f" src="1f9dc-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_fairy: { + keywords: [ "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddda\u200d\u2640\ufe0f" src="1f9da-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_fairy: { + keywords: [ "man", "male" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddda\u200d\u2642\ufe0f" src="1f9da-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + angel: { + keywords: [ "heaven", "wings", "halo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7c" src="1f47c.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + pregnant_woman: { + keywords: [ "baby" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd30" src="1f930.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + breastfeeding: { + keywords: [ "nursing", "baby" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd31" src="1f931.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + princess: { + keywords: [ "girl", "woman", "female", "blond", "crown", "royal", "queen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc78" src="1f478.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + prince: { + keywords: [ "boy", "man", "male", "crown", "royal", "king" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd34" src="1f934.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + bride_with_veil: { + keywords: [ "couple", "marriage", "wedding", "woman", "bride" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc70" src="1f470.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_in_tuxedo: { + keywords: [ "couple", "marriage", "wedding", "groom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd35" src="1f935.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + running_woman: { + keywords: [ "woman", "walking", "exercise", "race", "running", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc3\u200d\u2640\ufe0f" src="1f3c3-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + running_man: { + keywords: [ "man", "walking", "exercise", "race", "running" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc3" src="1f3c3.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + walking_woman: { + keywords: [ "human", "feet", "steps", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb6\u200d\u2640\ufe0f" src="1f6b6-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + walking_man: { + keywords: [ "human", "feet", "steps" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb6" src="1f6b6.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + dancer: { + keywords: [ "female", "girl", "woman", "fun" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc83" src="1f483.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_dancing: { + keywords: [ "male", "boy", "fun", "dancer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd7a" src="1f57a.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + dancing_women: { + keywords: [ "female", "bunny", "women", "girls" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6f" src="1f46f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + dancing_men: { + keywords: [ "male", "bunny", "men", "boys" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6f\u200d\u2642\ufe0f" src="1f46f-200d-2642-fe0f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couple: { + keywords: [ "pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6b" src="1f46b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + two_men_holding_hands: { + keywords: [ "pair", "couple", "love", "like", "bromance", "friendship", "people", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6c" src="1f46c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + two_women_holding_hands: { + keywords: [ "pair", "friendship", "couple", "love", "like", "female", "people", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6d" src="1f46d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + bowing_woman: { + keywords: [ "woman", "female", "girl" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude47\u200d\u2640\ufe0f" src="1f647-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + bowing_man: { + keywords: [ "man", "male", "boy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude47" src="1f647.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_facepalming: { + keywords: [ "man", "male", "boy", "disbelief" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd26\u200d\u2642\ufe0f" src="1f926-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_facepalming: { + keywords: [ "woman", "female", "girl", "disbelief" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd26\u200d\u2640\ufe0f" src="1f926-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_shrugging: { + keywords: [ "woman", "female", "girl", "confused", "indifferent", "doubt" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd37" src="1f937.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_shrugging: { + keywords: [ "man", "male", "boy", "confused", "indifferent", "doubt" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd37\u200d\u2642\ufe0f" src="1f937-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + tipping_hand_woman: { + keywords: [ "female", "girl", "woman", "human", "information" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc81" src="1f481.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + tipping_hand_man: { + keywords: [ "male", "boy", "man", "human", "information" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc81\u200d\u2642\ufe0f" src="1f481-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + no_good_woman: { + keywords: [ "female", "girl", "woman", "nope" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude45" src="1f645.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + no_good_man: { + keywords: [ "male", "boy", "man", "nope" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude45\u200d\u2642\ufe0f" src="1f645-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + ok_woman: { + keywords: [ "women", "girl", "female", "pink", "human", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude46" src="1f646.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + ok_man: { + keywords: [ "men", "boy", "male", "blue", "human", "man" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude46\u200d\u2642\ufe0f" src="1f646-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raising_hand_woman: { + keywords: [ "female", "girl", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4b" src="1f64b.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + raising_hand_man: { + keywords: [ "male", "boy", "man" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4b\u200d\u2642\ufe0f" src="1f64b-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + pouting_woman: { + keywords: [ "female", "girl", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4e" src="1f64e.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + pouting_man: { + keywords: [ "male", "boy", "man" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4e\u200d\u2642\ufe0f" src="1f64e-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + frowning_woman: { + keywords: [ "female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4d" src="1f64d.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + frowning_man: { + keywords: [ "male", "boy", "man", "sad", "depressed", "discouraged", "unhappy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4d\u200d\u2642\ufe0f" src="1f64d-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + haircut_woman: { + keywords: [ "female", "girl", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc87" src="1f487.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + haircut_man: { + keywords: [ "male", "boy", "man" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc87\u200d\u2642\ufe0f" src="1f487-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + massage_woman: { + keywords: [ "female", "girl", "woman", "head" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc86" src="1f486.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + massage_man: { + keywords: [ "male", "boy", "man", "head" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc86\u200d\u2642\ufe0f" src="1f486-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + woman_in_steamy_room: { + keywords: [ "female", "woman", "spa", "steamroom", "sauna" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd6\u200d\u2640\ufe0f" src="1f9d6-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + man_in_steamy_room: { + keywords: [ "male", "man", "spa", "steamroom", "sauna" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd6\u200d\u2642\ufe0f" src="1f9d6-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "people" + }, + couple_with_heart_woman_man: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc91" src="1f491.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couple_with_heart_woman_woman: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc69" src="1f469-200d-2764-fe0f-200d-1f469.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couple_with_heart_man_man: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68" src="1f468-200d-2764-fe0f-200d-1f468.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_man_woman: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8f" src="1f48f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_woman_woman: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69" src="1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_man_man: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68" src="1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_boy: { + keywords: [ "home", "parents", "child", "mom", "dad", "father", "mother", "people", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6a" src="1f46a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl: { + keywords: [ "home", "parents", "people", "human", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67" src="1f468-200d-1f469-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f469-200d-1f467-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f469-200d-1f466-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f469-200d-1f467-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67" src="1f469-200d-1f469-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f467-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f466-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f469-200d-1f469-200d-1f467-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67" src="1f468-200d-1f468-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f467-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f466-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f468-200d-1f467-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_boy: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc66" src="1f469-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67" src="1f469-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f469-200d-1f467-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_boy_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f469-200d-1f466-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl_girl: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f469-200d-1f467-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_boy: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc66" src="1f468-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67" src="1f468-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f467-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_boy_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f466-200d-1f466.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl_girl: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f467-200d-1f467.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + yarn: { + keywords: [ "ball", "crochet", "knit" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf6" src="1f9f6.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + thread: { + keywords: [ "needle", "sewing", "spool", "string" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf5" src="1f9f5.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + coat: { + keywords: [ "jacket" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde5" src="1f9e5.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + labcoat: { + keywords: [ "doctor", "experiment", "scientist", "chemist" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7c" src="1f97c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + womans_clothes: { + keywords: [ "fashion", "shopping_bags", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5a" src="1f45a.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + tshirt: { + keywords: [ "fashion", "cloth", "casual", "shirt", "tee" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc55" src="1f455.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + jeans: { + keywords: [ "fashion", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc56" src="1f456.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + necktie: { + keywords: [ "shirt", "suitup", "formal", "fashion", "cloth", "business" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc54" src="1f454.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + dress: { + keywords: [ "clothes", "fashion", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc57" src="1f457.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + bikini: { + keywords: [ "swimming", "female", "woman", "girl", "fashion", "beach", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc59" src="1f459.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kimono: { + keywords: [ "dress", "fashion", "women", "female", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc58" src="1f458.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + lipstick: { + keywords: [ "female", "girl", "fashion", "woman" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc84" src="1f484.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + kiss: { + keywords: [ "face", "lips", "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8b" src="1f48b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + footprints: { + keywords: [ "feet", "tracking", "walking", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc63" src="1f463.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + flat_shoe: { + keywords: [ "ballet", "slip-on", "slipper" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7f" src="1f97f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + high_heel: { + keywords: [ "fashion", "shoes", "female", "pumps", "stiletto" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc60" src="1f460.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + sandal: { + keywords: [ "shoes", "fashion", "flip flops" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc61" src="1f461.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + boot: { + keywords: [ "shoes", "fashion" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc62" src="1f462.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + mans_shoe: { + keywords: [ "fashion", "male" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5e" src="1f45e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + athletic_shoe: { + keywords: [ "shoes", "sports", "sneakers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5f" src="1f45f.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + hiking_boot: { + keywords: [ "backpacking", "camping", "hiking" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7e" src="1f97e.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + socks: { + keywords: [ "stockings", "clothes" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde6" src="1f9e6.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + gloves: { + keywords: [ "hands", "winter", "clothes" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde4" src="1f9e4.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + scarf: { + keywords: [ "neck", "winter", "clothes" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde3" src="1f9e3.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + womans_hat: { + keywords: [ "fashion", "accessories", "female", "lady", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc52" src="1f452.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + tophat: { + keywords: [ "magic", "gentleman", "classy", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa9" src="1f3a9.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + billed_hat: { + keywords: [ "cap", "baseball" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde2" src="1f9e2.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + rescue_worker_helmet: { + keywords: [ "construction", "build" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d1" src="26d1.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + mortar_board: { + keywords: [ "school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf93" src="1f393.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + crown: { + keywords: [ "king", "kod", "leader", "royalty", "lord" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc51" src="1f451.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + school_satchel: { + keywords: [ "student", "education", "bag", "backpack" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf92" src="1f392.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + luggage: { + keywords: [ "packing", "travel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf3" src="1f9f3.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + pouch: { + keywords: [ "bag", "accessories", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5d" src="1f45d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + purse: { + keywords: [ "fashion", "accessories", "money", "sales", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5b" src="1f45b.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + handbag: { + keywords: [ "fashion", "accessory", "accessories", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5c" src="1f45c.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + briefcase: { + keywords: [ "business", "documents", "work", "law", "legal", "job", "career" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbc" src="1f4bc.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + eyeglasses: { + keywords: [ "fashion", "accessories", "eyesight", "nerdy", "dork", "geek" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc53" src="1f453.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + dark_sunglasses: { + keywords: [ "face", "cool", "accessories" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd76" src="1f576.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + goggles: { + keywords: [ "eyes", "protection", "safety" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7d" src="1f97d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + ring: { + keywords: [ "wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8d" src="1f48d.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + closed_umbrella: { + keywords: [ "weather", "rain", "drizzle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf02" src="1f302.png"/>', + fitzpatrick_scale: false, + category: "people" + }, + dog: { + keywords: [ "animal", "friend", "nature", "woof", "puppy", "pet", "faithful" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc36" src="1f436.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cat: { + keywords: [ "animal", "meow", "nature", "pet", "kitten" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc31" src="1f431.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mouse: { + keywords: [ "animal", "nature", "cheese_wedge", "rodent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2d" src="1f42d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hamster: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc39" src="1f439.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rabbit: { + keywords: [ "animal", "nature", "pet", "spring", "magic", "bunny" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc30" src="1f430.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fox_face: { + keywords: [ "animal", "nature", "face" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8a" src="1f98a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bear: { + keywords: [ "animal", "nature", "wild" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3b" src="1f43b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + panda_face: { + keywords: [ "animal", "nature", "panda" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3c" src="1f43c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + koala: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc28" src="1f428.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tiger: { + keywords: [ "animal", "cat", "danger", "wild", "nature", "roar" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2f" src="1f42f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lion: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd81" src="1f981.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cow: { + keywords: [ "beef", "ox", "animal", "nature", "moo", "milk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2e" src="1f42e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig: { + keywords: [ "animal", "oink", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc37" src="1f437.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig_nose: { + keywords: [ "animal", "oink" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3d" src="1f43d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + frog: { + keywords: [ "animal", "nature", "croak", "toad" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc38" src="1f438.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + squid: { + keywords: [ "animal", "nature", "ocean", "sea" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd91" src="1f991.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + octopus: { + keywords: [ "animal", "creature", "ocean", "sea", "nature", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc19" src="1f419.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shrimp: { + keywords: [ "animal", "ocean", "nature", "seafood" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd90" src="1f990.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + monkey_face: { + keywords: [ "animal", "nature", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc35" src="1f435.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + gorilla: { + keywords: [ "animal", "nature", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8d" src="1f98d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + see_no_evil: { + keywords: [ "monkey", "animal", "nature", "haha" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude48" src="1f648.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hear_no_evil: { + keywords: [ "animal", "monkey", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude49" src="1f649.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + speak_no_evil: { + keywords: [ "monkey", "animal", "nature", "omg" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4a" src="1f64a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + monkey: { + keywords: [ "animal", "nature", "banana", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc12" src="1f412.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chicken: { + keywords: [ "animal", "cluck", "nature", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc14" src="1f414.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + penguin: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc27" src="1f427.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bird: { + keywords: [ "animal", "nature", "fly", "tweet", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc26" src="1f426.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + baby_chick: { + keywords: [ "animal", "chicken", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc24" src="1f424.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hatching_chick: { + keywords: [ "animal", "chicken", "egg", "born", "baby", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc23" src="1f423.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hatched_chick: { + keywords: [ "animal", "chicken", "baby", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc25" src="1f425.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + duck: { + keywords: [ "animal", "nature", "bird", "mallard" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd86" src="1f986.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + eagle: { + keywords: [ "animal", "nature", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd85" src="1f985.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + owl: { + keywords: [ "animal", "nature", "bird", "hoot" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd89" src="1f989.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bat: { + keywords: [ "animal", "nature", "blind", "vampire" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd87" src="1f987.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wolf: { + keywords: [ "animal", "nature", "wild" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3a" src="1f43a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + boar: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc17" src="1f417.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + horse: { + keywords: [ "animal", "brown", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc34" src="1f434.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + unicorn: { + keywords: [ "animal", "nature", "mystical" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd84" src="1f984.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + honeybee: { + keywords: [ "animal", "insect", "nature", "bug", "spring", "honey" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1d" src="1f41d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bug: { + keywords: [ "animal", "insect", "nature", "worm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1b" src="1f41b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + butterfly: { + keywords: [ "animal", "insect", "nature", "caterpillar" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8b" src="1f98b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snail: { + keywords: [ "slow", "animal", "shell" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0c" src="1f40c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + beetle: { + keywords: [ "animal", "insect", "nature", "ladybug" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1e" src="1f41e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ant: { + keywords: [ "animal", "insect", "nature", "bug" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1c" src="1f41c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + grasshopper: { + keywords: [ "animal", "cricket", "chirp" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd97" src="1f997.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + spider: { + keywords: [ "animal", "arachnid" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd77" src="1f577.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + scorpion: { + keywords: [ "animal", "arachnid" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd82" src="1f982.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crab: { + keywords: [ "animal", "crustacean" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd80" src="1f980.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snake: { + keywords: [ "animal", "evil", "nature", "hiss", "python" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0d" src="1f40d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lizard: { + keywords: [ "animal", "nature", "reptile" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8e" src="1f98e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + "t-rex": { + keywords: [ "animal", "nature", "dinosaur", "tyrannosaurus", "extinct" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd96" src="1f996.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sauropod: { + keywords: [ "animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd95" src="1f995.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + turtle: { + keywords: [ "animal", "slow", "nature", "tortoise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc22" src="1f422.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tropical_fish: { + keywords: [ "animal", "swim", "ocean", "beach", "nemo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc20" src="1f420.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fish: { + keywords: [ "animal", "food", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1f" src="1f41f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + blowfish: { + keywords: [ "animal", "nature", "food", "sea", "ocean" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc21" src="1f421.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dolphin: { + keywords: [ "animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2c" src="1f42c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shark: { + keywords: [ "animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd88" src="1f988.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + whale: { + keywords: [ "animal", "nature", "sea", "ocean" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc33" src="1f433.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + whale2: { + keywords: [ "animal", "nature", "sea", "ocean" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0b" src="1f40b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crocodile: { + keywords: [ "animal", "nature", "reptile", "lizard", "alligator" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0a" src="1f40a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + leopard: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc06" src="1f406.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + zebra: { + keywords: [ "animal", "nature", "stripes", "safari" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd93" src="1f993.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tiger2: { + keywords: [ "animal", "nature", "roar" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc05" src="1f405.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + water_buffalo: { + keywords: [ "animal", "nature", "ox", "cow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc03" src="1f403.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ox: { + keywords: [ "animal", "cow", "beef" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc02" src="1f402.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cow2: { + keywords: [ "beef", "ox", "animal", "nature", "moo", "milk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc04" src="1f404.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + deer: { + keywords: [ "animal", "nature", "horns", "venison" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8c" src="1f98c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dromedary_camel: { + keywords: [ "animal", "hot", "desert", "hump" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2a" src="1f42a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + camel: { + keywords: [ "animal", "nature", "hot", "desert", "hump" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2b" src="1f42b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + giraffe: { + keywords: [ "animal", "nature", "spots", "safari" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd92" src="1f992.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + elephant: { + keywords: [ "animal", "nature", "nose", "th", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc18" src="1f418.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rhinoceros: { + keywords: [ "animal", "nature", "horn" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8f" src="1f98f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + goat: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc10" src="1f410.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ram: { + keywords: [ "animal", "sheep", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0f" src="1f40f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sheep: { + keywords: [ "animal", "nature", "wool", "shipit" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc11" src="1f411.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + racehorse: { + keywords: [ "animal", "gamble", "luck" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0e" src="1f40e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig2: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc16" src="1f416.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rat: { + keywords: [ "animal", "mouse", "rodent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc00" src="1f400.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mouse2: { + keywords: [ "animal", "nature", "rodent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc01" src="1f401.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rooster: { + keywords: [ "animal", "nature", "chicken" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc13" src="1f413.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + turkey: { + keywords: [ "animal", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd83" src="1f983.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dove: { + keywords: [ "animal", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4a" src="1f54a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dog2: { + keywords: [ "animal", "nature", "friend", "doge", "pet", "faithful" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc15" src="1f415.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + poodle: { + keywords: [ "dog", "animal", "101", "nature", "pet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc29" src="1f429.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cat2: { + keywords: [ "animal", "meow", "pet", "cats" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc08" src="1f408.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rabbit2: { + keywords: [ "animal", "nature", "pet", "magic", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc07" src="1f407.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chipmunk: { + keywords: [ "animal", "nature", "rodent", "squirrel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3f" src="1f43f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hedgehog: { + keywords: [ "animal", "nature", "spiny" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd94" src="1f994.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + raccoon: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9d" src="1f99d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + llama: { + keywords: [ "animal", "nature", "alpaca" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd99" src="1f999.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hippopotamus: { + keywords: [ "animal", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9b" src="1f99b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + kangaroo: { + keywords: [ "animal", "nature", "australia", "joey", "hop", "marsupial" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd98" src="1f998.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + badger: { + keywords: [ "animal", "nature", "honey" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda1" src="1f9a1.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + swan: { + keywords: [ "animal", "nature", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda2" src="1f9a2.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + peacock: { + keywords: [ "animal", "nature", "peahen", "bird" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9a" src="1f99a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + parrot: { + keywords: [ "animal", "nature", "bird", "pirate", "talk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9c" src="1f99c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lobster: { + keywords: [ "animal", "nature", "bisque", "claws", "seafood" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9e" src="1f99e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mosquito: { + keywords: [ "animal", "nature", "insect", "malaria" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9f" src="1f99f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + paw_prints: { + keywords: [ "animal", "tracking", "footprints", "dog", "cat", "pet", "feet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3e" src="1f43e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dragon: { + keywords: [ "animal", "myth", "nature", "chinese", "green" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc09" src="1f409.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dragon_face: { + keywords: [ "animal", "myth", "nature", "chinese", "green" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc32" src="1f432.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cactus: { + keywords: [ "vegetable", "plant", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf35" src="1f335.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + christmas_tree: { + keywords: [ "festival", "vacation", "december", "xmas", "celebration" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf84" src="1f384.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + evergreen_tree: { + keywords: [ "plant", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf32" src="1f332.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + deciduous_tree: { + keywords: [ "plant", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf33" src="1f333.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + palm_tree: { + keywords: [ "plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf34" src="1f334.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + seedling: { + keywords: [ "plant", "nature", "grass", "lawn", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf31" src="1f331.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + herb: { + keywords: [ "vegetable", "plant", "medicine", "weed", "grass", "lawn" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3f" src="1f33f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shamrock: { + keywords: [ "vegetable", "plant", "nature", "irish", "clover" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2618" src="2618.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + four_leaf_clover: { + keywords: [ "vegetable", "plant", "nature", "lucky", "irish" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf40" src="1f340.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bamboo: { + keywords: [ "plant", "nature", "vegetable", "panda", "pine_decoration" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8d" src="1f38d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tanabata_tree: { + keywords: [ "plant", "nature", "branch", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8b" src="1f38b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + leaves: { + keywords: [ "nature", "plant", "tree", "vegetable", "grass", "lawn", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf43" src="1f343.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fallen_leaf: { + keywords: [ "nature", "plant", "vegetable", "leaves" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf42" src="1f342.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + maple_leaf: { + keywords: [ "nature", "plant", "vegetable", "ca", "fall" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf41" src="1f341.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ear_of_rice: { + keywords: [ "nature", "plant" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3e" src="1f33e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hibiscus: { + keywords: [ "plant", "vegetable", "flowers", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3a" src="1f33a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sunflower: { + keywords: [ "nature", "plant", "fall" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3b" src="1f33b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rose: { + keywords: [ "flowers", "valentines", "love", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf39" src="1f339.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wilted_flower: { + keywords: [ "plant", "nature", "flower" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd40" src="1f940.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tulip: { + keywords: [ "flowers", "plant", "nature", "summer", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf37" src="1f337.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + blossom: { + keywords: [ "nature", "flowers", "yellow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3c" src="1f33c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cherry_blossom: { + keywords: [ "nature", "plant", "spring", "flower" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf38" src="1f338.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bouquet: { + keywords: [ "flowers", "nature", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc90" src="1f490.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mushroom: { + keywords: [ "plant", "vegetable" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf44" src="1f344.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chestnut: { + keywords: [ "food", "squirrel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf30" src="1f330.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + jack_o_lantern: { + keywords: [ "halloween", "light", "pumpkin", "creepy", "fall" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf83" src="1f383.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shell: { + keywords: [ "nature", "sea", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1a" src="1f41a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + spider_web: { + keywords: [ "animal", "insect", "arachnid", "silk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd78" src="1f578.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_americas: { + keywords: [ "globe", "world", "USA", "international" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0e" src="1f30e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_africa: { + keywords: [ "globe", "world", "international" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0d" src="1f30d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_asia: { + keywords: [ "globe", "world", "east", "international" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0f" src="1f30f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + full_moon: { + keywords: [ "nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf15" src="1f315.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waning_gibbous_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf16" src="1f316.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + last_quarter_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf17" src="1f317.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waning_crescent_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf18" src="1f318.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + new_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf11" src="1f311.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waxing_crescent_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf12" src="1f312.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + first_quarter_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf13" src="1f313.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waxing_gibbous_moon: { + keywords: [ "nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf14" src="1f314.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + new_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1a" src="1f31a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + full_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1d" src="1f31d.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + first_quarter_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1b" src="1f31b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + last_quarter_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1c" src="1f31c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_with_face: { + keywords: [ "nature", "morning", "sky" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1e" src="1f31e.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crescent_moon: { + keywords: [ "night", "sleep", "sky", "evening", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf19" src="1f319.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + star: { + keywords: [ "night", "yellow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b50" src="2b50.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + star2: { + keywords: [ "night", "sparkle", "awesome", "good", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1f" src="1f31f.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dizzy: { + keywords: [ "star", "sparkle", "shoot", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcab" src="1f4ab.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sparkles: { + keywords: [ "stars", "shine", "shiny", "cool", "awesome", "good", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2728" src="2728.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + comet: { + keywords: [ "space" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2604" src="2604.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sunny: { + keywords: [ "weather", "nature", "brightness", "summer", "beach", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2600\ufe0f" src="2600.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_small_cloud: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf24" src="1f324.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + partly_sunny: { + keywords: [ "weather", "nature", "cloudy", "morning", "fall", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c5" src="26c5.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_large_cloud: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf25" src="1f325.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_rain_cloud: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf26" src="1f326.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud: { + keywords: [ "weather", "sky" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2601\ufe0f" src="2601.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_rain: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf27" src="1f327.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_lightning_and_rain: { + keywords: [ "weather", "lightning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c8" src="26c8.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_lightning: { + keywords: [ "weather", "thunder" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf29" src="1f329.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + zap: { + keywords: [ "thunder", "weather", "lightning bolt", "fast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26a1" src="26a1.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fire: { + keywords: [ "hot", "cook", "flame" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd25" src="1f525.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + boom: { + keywords: [ "bomb", "explode", "explosion", "collision", "blown" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca5" src="1f4a5.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowflake: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2744\ufe0f" src="2744.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_snow: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf28" src="1f328.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowman: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c4" src="26c4.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowman_with_snow: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas", "frozen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2603" src="2603.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wind_face: { + keywords: [ "gust", "air" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2c" src="1f32c.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dash: { + keywords: [ "wind", "air", "fast", "shoo", "fart", "smoke", "puff" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca8" src="1f4a8.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tornado: { + keywords: [ "weather", "cyclone", "twister" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2a" src="1f32a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fog: { + keywords: [ "weather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2b" src="1f32b.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + open_umbrella: { + keywords: [ "weather", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2602" src="2602.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + umbrella: { + keywords: [ "rainy", "weather", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2614" src="2614.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + droplet: { + keywords: [ "water", "drip", "faucet", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca7" src="1f4a7.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sweat_drops: { + keywords: [ "water", "drip", "oops" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca6" src="1f4a6.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ocean: { + keywords: [ "sea", "water", "wave", "nature", "tsunami", "disaster" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0a" src="1f30a.png"/>', + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + green_apple: { + keywords: [ "fruit", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4f" src="1f34f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + apple: { + keywords: [ "fruit", "mac", "school" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4e" src="1f34e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pear: { + keywords: [ "fruit", "nature", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf50" src="1f350.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tangerine: { + keywords: [ "food", "fruit", "nature", "orange" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4a" src="1f34a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + lemon: { + keywords: [ "fruit", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4b" src="1f34b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + banana: { + keywords: [ "fruit", "food", "monkey" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4c" src="1f34c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + watermelon: { + keywords: [ "fruit", "food", "picnic", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf49" src="1f349.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + grapes: { + keywords: [ "fruit", "food", "wine" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf47" src="1f347.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + strawberry: { + keywords: [ "fruit", "food", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf53" src="1f353.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + melon: { + keywords: [ "fruit", "nature", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf48" src="1f348.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cherries: { + keywords: [ "food", "fruit" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf52" src="1f352.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + peach: { + keywords: [ "fruit", "nature", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf51" src="1f351.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pineapple: { + keywords: [ "fruit", "nature", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4d" src="1f34d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + coconut: { + keywords: [ "fruit", "nature", "food", "palm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd65" src="1f965.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + kiwi_fruit: { + keywords: [ "fruit", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5d" src="1f95d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + mango: { + keywords: [ "fruit", "food", "tropical" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6d" src="1f96d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + avocado: { + keywords: [ "fruit", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd51" src="1f951.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + broccoli: { + keywords: [ "fruit", "food", "vegetable" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd66" src="1f966.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tomato: { + keywords: [ "fruit", "vegetable", "nature", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf45" src="1f345.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + eggplant: { + keywords: [ "vegetable", "nature", "food", "aubergine" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf46" src="1f346.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cucumber: { + keywords: [ "fruit", "food", "pickle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd52" src="1f952.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + carrot: { + keywords: [ "vegetable", "food", "orange" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd55" src="1f955.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hot_pepper: { + keywords: [ "food", "spicy", "chilli", "chili" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf36" src="1f336.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + potato: { + keywords: [ "food", "tuber", "vegatable", "starch" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd54" src="1f954.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + corn: { + keywords: [ "food", "vegetable", "plant" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3d" src="1f33d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + leafy_greens: { + keywords: [ "food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6c" src="1f96c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sweet_potato: { + keywords: [ "food", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf60" src="1f360.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + peanuts: { + keywords: [ "food", "nut" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5c" src="1f95c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + honey_pot: { + keywords: [ "bees", "sweet", "kitchen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6f" src="1f36f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + croissant: { + keywords: [ "food", "bread", "french" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd50" src="1f950.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bread: { + keywords: [ "food", "wheat", "breakfast", "toast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5e" src="1f35e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + baguette_bread: { + keywords: [ "food", "bread", "french" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd56" src="1f956.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bagel: { + keywords: [ "food", "bread", "bakery", "schmear" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6f" src="1f96f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pretzel: { + keywords: [ "food", "bread", "twisted" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd68" src="1f968.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cheese: { + keywords: [ "food", "chadder" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc0" src="1f9c0.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + egg: { + keywords: [ "food", "chicken", "breakfast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5a" src="1f95a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bacon: { + keywords: [ "food", "breakfast", "pork", "pig", "meat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd53" src="1f953.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + steak: { + keywords: [ "food", "cow", "meat", "cut", "chop", "lambchop", "porkchop" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd69" src="1f969.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pancakes: { + keywords: [ "food", "breakfast", "flapjacks", "hotcakes" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5e" src="1f95e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + poultry_leg: { + keywords: [ "food", "meat", "drumstick", "bird", "chicken", "turkey" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf57" src="1f357.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + meat_on_bone: { + keywords: [ "good", "food", "drumstick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf56" src="1f356.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bone: { + keywords: [ "skeleton" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb4" src="1f9b4.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fried_shrimp: { + keywords: [ "food", "animal", "appetizer", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf64" src="1f364.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fried_egg: { + keywords: [ "food", "breakfast", "kitchen", "egg" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf73" src="1f373.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hamburger: { + keywords: [ "meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf54" src="1f354.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fries: { + keywords: [ "chips", "snack", "fast food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5f" src="1f35f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + stuffed_flatbread: { + keywords: [ "food", "flatbread", "stuffed", "gyro" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd59" src="1f959.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hotdog: { + keywords: [ "food", "frankfurter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2d" src="1f32d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pizza: { + keywords: [ "food", "party" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf55" src="1f355.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sandwich: { + keywords: [ "food", "lunch", "bread" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6a" src="1f96a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + canned_food: { + keywords: [ "food", "soup" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6b" src="1f96b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + spaghetti: { + keywords: [ "food", "italian", "noodle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5d" src="1f35d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + taco: { + keywords: [ "food", "mexican" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2e" src="1f32e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + burrito: { + keywords: [ "food", "mexican" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2f" src="1f32f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + green_salad: { + keywords: [ "food", "healthy", "lettuce" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd57" src="1f957.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + shallow_pan_of_food: { + keywords: [ "food", "cooking", "casserole", "paella" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd58" src="1f958.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + ramen: { + keywords: [ "food", "japanese", "noodle", "chopsticks" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5c" src="1f35c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + stew: { + keywords: [ "food", "meat", "soup" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf72" src="1f372.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fish_cake: { + keywords: [ "food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf65" src="1f365.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fortune_cookie: { + keywords: [ "food", "prophecy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd60" src="1f960.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sushi: { + keywords: [ "food", "fish", "japanese", "rice" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf63" src="1f363.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bento: { + keywords: [ "food", "japanese", "box" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf71" src="1f371.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + curry: { + keywords: [ "food", "spicy", "hot", "indian" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5b" src="1f35b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice_ball: { + keywords: [ "food", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf59" src="1f359.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice: { + keywords: [ "food", "china", "asian" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5a" src="1f35a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice_cracker: { + keywords: [ "food", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf58" src="1f358.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + oden: { + keywords: [ "food", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf62" src="1f362.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + dango: { + keywords: [ "food", "dessert", "sweet", "japanese", "barbecue", "meat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf61" src="1f361.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + shaved_ice: { + keywords: [ "hot", "dessert", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf67" src="1f367.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + ice_cream: { + keywords: [ "food", "hot", "dessert" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf68" src="1f368.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + icecream: { + keywords: [ "food", "hot", "dessert", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf66" src="1f366.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pie: { + keywords: [ "food", "dessert", "pastry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd67" src="1f967.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cake: { + keywords: [ "food", "dessert" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf70" src="1f370.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cupcake: { + keywords: [ "food", "dessert", "bakery", "sweet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc1" src="1f9c1.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + moon_cake: { + keywords: [ "food", "autumn" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6e" src="1f96e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + birthday: { + keywords: [ "food", "dessert", "cake" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf82" src="1f382.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + custard: { + keywords: [ "dessert", "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6e" src="1f36e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + candy: { + keywords: [ "snack", "dessert", "sweet", "lolly" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6c" src="1f36c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + lollipop: { + keywords: [ "food", "snack", "candy", "sweet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6d" src="1f36d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + chocolate_bar: { + keywords: [ "food", "snack", "dessert", "sweet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6b" src="1f36b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + popcorn: { + keywords: [ "food", "movie theater", "films", "snack" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7f" src="1f37f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + dumpling: { + keywords: [ "food", "empanada", "pierogi", "potsticker" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5f" src="1f95f.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + doughnut: { + keywords: [ "food", "dessert", "snack", "sweet", "donut" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf69" src="1f369.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cookie: { + keywords: [ "food", "snack", "oreo", "chocolate", "sweet", "dessert" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6a" src="1f36a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + milk_glass: { + keywords: [ "beverage", "drink", "cow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5b" src="1f95b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + beer: { + keywords: [ "relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7a" src="1f37a.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + beers: { + keywords: [ "relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7b" src="1f37b.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + clinking_glasses: { + keywords: [ "beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd42" src="1f942.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + wine_glass: { + keywords: [ "drink", "beverage", "drunk", "alcohol", "booze" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf77" src="1f377.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tumbler_glass: { + keywords: [ "drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd43" src="1f943.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cocktail: { + keywords: [ "drink", "drunk", "alcohol", "beverage", "booze", "mojito" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf78" src="1f378.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tropical_drink: { + keywords: [ "beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf79" src="1f379.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + champagne: { + keywords: [ "drink", "wine", "bottle", "celebration" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7e" src="1f37e.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sake: { + keywords: [ "wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf76" src="1f376.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tea: { + keywords: [ "drink", "bowl", "breakfast", "green", "british" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf75" src="1f375.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cup_with_straw: { + keywords: [ "drink", "soda" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd64" src="1f964.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + coffee: { + keywords: [ "beverage", "caffeine", "latte", "espresso" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2615" src="2615.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + baby_bottle: { + keywords: [ "food", "container", "milk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7c" src="1f37c.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + salt: { + keywords: [ "condiment", "shaker" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc2" src="1f9c2.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + spoon: { + keywords: [ "cutlery", "kitchen", "tableware" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd44" src="1f944.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fork_and_knife: { + keywords: [ "cutlery", "kitchen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf74" src="1f374.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + plate_with_cutlery: { + keywords: [ "food", "eat", "meal", "lunch", "dinner", "restaurant" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7d" src="1f37d.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bowl_with_spoon: { + keywords: [ "food", "breakfast", "cereal", "oatmeal", "porridge" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd63" src="1f963.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + takeout_box: { + keywords: [ "food", "leftovers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd61" src="1f961.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + chopsticks: { + keywords: [ "food" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd62" src="1f962.png"/>', + fitzpatrick_scale: false, + category: "food_and_drink" + }, + soccer: { + keywords: [ "sports", "football" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26bd" src="26bd.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + basketball: { + keywords: [ "sports", "balls", "NBA" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc0" src="1f3c0.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + football: { + keywords: [ "sports", "balls", "NFL" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc8" src="1f3c8.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + baseball: { + keywords: [ "sports", "balls" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26be" src="26be.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + softball: { + keywords: [ "sports", "balls" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4e" src="1f94e.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + tennis: { + keywords: [ "sports", "balls", "green" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbe" src="1f3be.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + volleyball: { + keywords: [ "sports", "balls" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd0" src="1f3d0.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + rugby_football: { + keywords: [ "sports", "team" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc9" src="1f3c9.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + flying_disc: { + keywords: [ "sports", "frisbee", "ultimate" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4f" src="1f94f.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + "8ball": { + keywords: [ "pool", "hobby", "game", "luck", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb1" src="1f3b1.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + golf: { + keywords: [ "sports", "business", "flag", "hole", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f3" src="26f3.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + golfing_woman: { + keywords: [ "sports", "business", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcc\ufe0f\u200d\u2640\ufe0f" src="1f3cc-fe0f-200d-2640-fe0f.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + golfing_man: { + keywords: [ "sports", "business" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcc" src="1f3cc.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + ping_pong: { + keywords: [ "sports", "pingpong" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd3" src="1f3d3.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + badminton: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff8" src="1f3f8.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + goal_net: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd45" src="1f945.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + ice_hockey: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd2" src="1f3d2.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + field_hockey: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd1" src="1f3d1.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + lacrosse: { + keywords: [ "sports", "ball", "stick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4d" src="1f94d.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + cricket: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcf" src="1f3cf.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + ski: { + keywords: [ "sports", "winter", "cold", "snow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbf" src="1f3bf.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + skier: { + keywords: [ "sports", "winter", "snow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f7" src="26f7.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + snowboarder: { + keywords: [ "sports", "winter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc2" src="1f3c2.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + person_fencing: { + keywords: [ "sports", "fencing", "sword" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3a" src="1f93a.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + women_wrestling: { + keywords: [ "sports", "wrestlers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3c\u200d\u2640\ufe0f" src="1f93c-200d-2640-fe0f.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + men_wrestling: { + keywords: [ "sports", "wrestlers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3c\u200d\u2642\ufe0f" src="1f93c-200d-2642-fe0f.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + woman_cartwheeling: { + keywords: [ "gymnastics" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd38\u200d\u2640\ufe0f" src="1f938-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + man_cartwheeling: { + keywords: [ "gymnastics" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd38\u200d\u2642\ufe0f" src="1f938-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + woman_playing_handball: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3e\u200d\u2640\ufe0f" src="1f93e-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + man_playing_handball: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3e\u200d\u2642\ufe0f" src="1f93e-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + ice_skate: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f8" src="26f8.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + curling_stone: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4c" src="1f94c.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + skateboard: { + keywords: [ "board" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef9" src="1f6f9.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + sled: { + keywords: [ "sleigh", "luge", "toboggan" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef7" src="1f6f7.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + bow_and_arrow: { + keywords: [ "sports" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff9" src="1f3f9.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + fishing_pole_and_fish: { + keywords: [ "food", "hobby", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa3" src="1f3a3.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + boxing_glove: { + keywords: [ "sports", "fighting" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4a" src="1f94a.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + martial_arts_uniform: { + keywords: [ "judo", "karate", "taekwondo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4b" src="1f94b.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + rowing_woman: { + keywords: [ "sports", "hobby", "water", "ship", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea3\u200d\u2640\ufe0f" src="1f6a3-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + rowing_man: { + keywords: [ "sports", "hobby", "water", "ship" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea3" src="1f6a3.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + climbing_woman: { + keywords: [ "sports", "hobby", "woman", "female", "rock" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd7\u200d\u2640\ufe0f" src="1f9d7-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + climbing_man: { + keywords: [ "sports", "hobby", "man", "male", "rock" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd7\u200d\u2642\ufe0f" src="1f9d7-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + swimming_woman: { + keywords: [ "sports", "exercise", "human", "athlete", "water", "summer", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfca\u200d\u2640\ufe0f" src="1f3ca-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + swimming_man: { + keywords: [ "sports", "exercise", "human", "athlete", "water", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfca" src="1f3ca.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + woman_playing_water_polo: { + keywords: [ "sports", "pool" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3d\u200d\u2640\ufe0f" src="1f93d-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + man_playing_water_polo: { + keywords: [ "sports", "pool" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3d\u200d\u2642\ufe0f" src="1f93d-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + woman_in_lotus_position: { + keywords: [ "woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd8\u200d\u2640\ufe0f" src="1f9d8-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + man_in_lotus_position: { + keywords: [ "man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd8\u200d\u2642\ufe0f" src="1f9d8-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + surfing_woman: { + keywords: [ "sports", "ocean", "sea", "summer", "beach", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc4\u200d\u2640\ufe0f" src="1f3c4-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + surfing_man: { + keywords: [ "sports", "ocean", "sea", "summer", "beach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc4" src="1f3c4.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + bath: { + keywords: [ "clean", "shower", "bathroom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec0" src="1f6c0.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + basketball_woman: { + keywords: [ "sports", "human", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f9\ufe0f\u200d\u2640\ufe0f" src="26f9-fe0f-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + basketball_man: { + keywords: [ "sports", "human" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f9" src="26f9.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + weight_lifting_woman: { + keywords: [ "sports", "training", "exercise", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcb\ufe0f\u200d\u2640\ufe0f" src="1f3cb-fe0f-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + weight_lifting_man: { + keywords: [ "sports", "training", "exercise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcb" src="1f3cb.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + biking_woman: { + keywords: [ "sports", "bike", "exercise", "hipster", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb4\u200d\u2640\ufe0f" src="1f6b4-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + biking_man: { + keywords: [ "sports", "bike", "exercise", "hipster" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb4" src="1f6b4.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + mountain_biking_woman: { + keywords: [ "transportation", "sports", "human", "race", "bike", "woman", "female" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb5\u200d\u2640\ufe0f" src="1f6b5-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + mountain_biking_man: { + keywords: [ "transportation", "sports", "human", "race", "bike" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb5" src="1f6b5.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + horse_racing: { + keywords: [ "animal", "betting", "competition", "gambling", "luck" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc7" src="1f3c7.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + business_suit_levitating: { + keywords: [ "suit", "business", "levitate", "hover", "jump" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd74" src="1f574.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + trophy: { + keywords: [ "win", "award", "contest", "place", "ftw", "ceremony" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc6" src="1f3c6.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + running_shirt_with_sash: { + keywords: [ "play", "pageant" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbd" src="1f3bd.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + medal_sports: { + keywords: [ "award", "winning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc5" src="1f3c5.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + medal_military: { + keywords: [ "award", "winning", "army" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf96" src="1f396.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + "1st_place_medal": { + keywords: [ "award", "winning", "first" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd47" src="1f947.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + "2nd_place_medal": { + keywords: [ "award", "second" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd48" src="1f948.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + "3rd_place_medal": { + keywords: [ "award", "third" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd49" src="1f949.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + reminder_ribbon: { + keywords: [ "sports", "cause", "support", "awareness" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf97" src="1f397.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + rosette: { + keywords: [ "flower", "decoration", "military" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff5" src="1f3f5.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + ticket: { + keywords: [ "event", "concert", "pass" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfab" src="1f3ab.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + tickets: { + keywords: [ "sports", "concert", "entrance" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9f" src="1f39f.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + performing_arts: { + keywords: [ "acting", "theater", "drama" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfad" src="1f3ad.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + art: { + keywords: [ "design", "paint", "draw", "colors" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa8" src="1f3a8.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + circus_tent: { + keywords: [ "festival", "carnival", "party" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfaa" src="1f3aa.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + woman_juggling: { + keywords: [ "juggle", "balance", "skill", "multitask" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd39\u200d\u2640\ufe0f" src="1f939-200d-2640-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + man_juggling: { + keywords: [ "juggle", "balance", "skill", "multitask" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd39\u200d\u2642\ufe0f" src="1f939-200d-2642-fe0f.png"/>', + fitzpatrick_scale: true, + category: "activity" + }, + microphone: { + keywords: [ "sound", "music", "PA", "sing", "talkshow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa4" src="1f3a4.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + headphones: { + keywords: [ "music", "score", "gadgets" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa7" src="1f3a7.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + musical_score: { + keywords: [ "treble", "clef", "compose" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbc" src="1f3bc.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + musical_keyboard: { + keywords: [ "piano", "instrument", "compose" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb9" src="1f3b9.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + drum: { + keywords: [ "music", "instrument", "drumsticks", "snare" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd41" src="1f941.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + saxophone: { + keywords: [ "music", "instrument", "jazz", "blues" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb7" src="1f3b7.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + trumpet: { + keywords: [ "music", "brass" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfba" src="1f3ba.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + guitar: { + keywords: [ "music", "instrument" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb8" src="1f3b8.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + violin: { + keywords: [ "music", "instrument", "orchestra", "symphony" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbb" src="1f3bb.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + clapper: { + keywords: [ "movie", "film", "record" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfac" src="1f3ac.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + video_game: { + keywords: [ "play", "console", "PS4", "controller" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfae" src="1f3ae.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + space_invader: { + keywords: [ "game", "arcade", "play" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7e" src="1f47e.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + dart: { + keywords: [ "game", "play", "bar", "target", "bullseye" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfaf" src="1f3af.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + game_die: { + keywords: [ "dice", "random", "tabletop", "play", "luck" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb2" src="1f3b2.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + chess_pawn: { + keywords: [ "expendable" ], + char: "\u265f", + fitzpatrick_scale: false, + category: "activity" + }, + slot_machine: { + keywords: [ "bet", "gamble", "vegas", "fruit machine", "luck", "casino" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb0" src="1f3b0.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + jigsaw: { + keywords: [ "interlocking", "puzzle", "piece" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde9" src="1f9e9.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + bowling: { + keywords: [ "sports", "fun", "play" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb3" src="1f3b3.png"/>', + fitzpatrick_scale: false, + category: "activity" + }, + red_car: { + keywords: [ "red", "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude97" src="1f697.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + taxi: { + keywords: [ "uber", "vehicle", "cars", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude95" src="1f695.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + blue_car: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude99" src="1f699.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bus: { + keywords: [ "car", "vehicle", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8c" src="1f68c.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + trolleybus: { + keywords: [ "bart", "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8e" src="1f68e.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + racing_car: { + keywords: [ "sports", "race", "fast", "formula", "f1" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfce" src="1f3ce.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + police_car: { + keywords: [ "vehicle", "cars", "transportation", "law", "legal", "enforcement" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude93" src="1f693.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ambulance: { + keywords: [ "health", "911", "hospital" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude91" src="1f691.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fire_engine: { + keywords: [ "transportation", "cars", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude92" src="1f692.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + minibus: { + keywords: [ "vehicle", "car", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude90" src="1f690.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + truck: { + keywords: [ "cars", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9a" src="1f69a.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + articulated_lorry: { + keywords: [ "vehicle", "cars", "transportation", "express" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9b" src="1f69b.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tractor: { + keywords: [ "vehicle", "car", "farming", "agriculture" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9c" src="1f69c.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + kick_scooter: { + keywords: [ "vehicle", "kick", "razor" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef4" src="1f6f4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motorcycle: { + keywords: [ "race", "sports", "fast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcd" src="1f3cd.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bike: { + keywords: [ "sports", "bicycle", "exercise", "hipster" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb2" src="1f6b2.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motor_scooter: { + keywords: [ "vehicle", "vespa", "sasha" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef5" src="1f6f5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rotating_light: { + keywords: [ "police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea8" src="1f6a8.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_police_car: { + keywords: [ "vehicle", "law", "legal", "enforcement", "911" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude94" src="1f694.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_bus: { + keywords: [ "vehicle", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8d" src="1f68d.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_automobile: { + keywords: [ "car", "vehicle", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude98" src="1f698.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_taxi: { + keywords: [ "vehicle", "cars", "uber" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude96" src="1f696.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + aerial_tramway: { + keywords: [ "transportation", "vehicle", "ski" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea1" src="1f6a1.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_cableway: { + keywords: [ "transportation", "vehicle", "ski" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea0" src="1f6a0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + suspension_railway: { + keywords: [ "vehicle", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9f" src="1f69f.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + railway_car: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude83" src="1f683.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + train: { + keywords: [ "transportation", "vehicle", "carriage", "public", "travel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8b" src="1f68b.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + monorail: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9d" src="1f69d.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bullettrain_side: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude84" src="1f684.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bullettrain_front: { + keywords: [ "transportation", "vehicle", "speed", "fast", "public", "travel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude85" src="1f685.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + light_rail: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude88" src="1f688.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_railway: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9e" src="1f69e.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + steam_locomotive: { + keywords: [ "transportation", "vehicle", "train" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude82" src="1f682.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + train2: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude86" src="1f686.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + metro: { + keywords: [ "transportation", "blue-square", "mrt", "underground", "tube" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude87" src="1f687.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tram: { + keywords: [ "transportation", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8a" src="1f68a.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + station: { + keywords: [ "transportation", "vehicle", "public" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude89" src="1f689.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flying_saucer: { + keywords: [ "transportation", "vehicle", "ufo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef8" src="1f6f8.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + helicopter: { + keywords: [ "transportation", "vehicle", "fly" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude81" src="1f681.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + small_airplane: { + keywords: [ "flight", "transportation", "fly", "vehicle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee9" src="1f6e9.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + airplane: { + keywords: [ "vehicle", "transportation", "flight", "fly" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2708\ufe0f" src="2708.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flight_departure: { + keywords: [ "airport", "flight", "landing" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeeb" src="1f6eb.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flight_arrival: { + keywords: [ "airport", "flight", "boarding" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeec" src="1f6ec.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sailboat: { + keywords: [ "ship", "summer", "transportation", "water", "sailing" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f5" src="26f5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motor_boat: { + keywords: [ "ship" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee5" src="1f6e5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + speedboat: { + keywords: [ "ship", "transportation", "vehicle", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea4" src="1f6a4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ferry: { + keywords: [ "boat", "ship", "yacht" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f4" src="26f4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + passenger_ship: { + keywords: [ "yacht", "cruise", "ferry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef3" src="1f6f3.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rocket: { + keywords: [ "launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude80" src="1f680.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + artificial_satellite: { + keywords: [ "communication", "gps", "orbit", "spaceflight", "NASA", "ISS" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef0" src="1f6f0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + seat: { + keywords: [ "sit", "airplane", "transport", "bus", "flight", "fly" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcba" src="1f4ba.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + canoe: { + keywords: [ "boat", "paddle", "water", "ship" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef6" src="1f6f6.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + anchor: { + keywords: [ "ship", "ferry", "sea", "boat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2693" src="2693.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + construction: { + keywords: [ "wip", "progress", "caution", "warning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea7" src="1f6a7.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fuelpump: { + keywords: [ "gas station", "petroleum" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26fd" src="26fd.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + busstop: { + keywords: [ "transportation", "wait" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8f" src="1f68f.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + vertical_traffic_light: { + keywords: [ "transportation", "driving" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea6" src="1f6a6.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + traffic_light: { + keywords: [ "transportation", "signal" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea5" src="1f6a5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + checkered_flag: { + keywords: [ "contest", "finishline", "race", "gokart" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc1" src="1f3c1.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ship: { + keywords: [ "transportation", "titanic", "deploy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea2" src="1f6a2.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ferris_wheel: { + keywords: [ "photo", "carnival", "londoneye" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa1" src="1f3a1.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + roller_coaster: { + keywords: [ "carnival", "playground", "photo", "fun" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa2" src="1f3a2.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + carousel_horse: { + keywords: [ "photo", "carnival" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa0" src="1f3a0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + building_construction: { + keywords: [ "wip", "working", "progress" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd7" src="1f3d7.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + foggy: { + keywords: [ "photo", "mountain" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf01" src="1f301.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tokyo_tower: { + keywords: [ "photo", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfc" src="1f5fc.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + factory: { + keywords: [ "building", "industry", "pollution", "smoke" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfed" src="1f3ed.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fountain: { + keywords: [ "photo", "summer", "water", "fresh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f2" src="26f2.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rice_scene: { + keywords: [ "photo", "japan", "asia", "tsukimi" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf91" src="1f391.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain: { + keywords: [ "photo", "nature", "environment" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f0" src="26f0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_snow: { + keywords: [ "photo", "nature", "environment", "winter", "cold" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd4" src="1f3d4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mount_fuji: { + keywords: [ "photo", "mountain", "nature", "japanese" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfb" src="1f5fb.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + volcano: { + keywords: [ "photo", "nature", "disaster" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0b" src="1f30b.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + japan: { + keywords: [ "nation", "country", "japanese", "asia" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfe" src="1f5fe.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + camping: { + keywords: [ "photo", "outdoors", "tent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd5" src="1f3d5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tent: { + keywords: [ "photo", "camping", "outdoors" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26fa" src="26fa.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + national_park: { + keywords: [ "photo", "environment", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfde" src="1f3de.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motorway: { + keywords: [ "road", "cupertino", "interstate", "highway" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee3" src="1f6e3.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + railway_track: { + keywords: [ "train", "transportation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee4" src="1f6e4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sunrise: { + keywords: [ "morning", "view", "vacation", "photo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf05" src="1f305.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sunrise_over_mountains: { + keywords: [ "view", "vacation", "photo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf04" src="1f304.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + desert: { + keywords: [ "photo", "warm", "saharah" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdc" src="1f3dc.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + beach_umbrella: { + keywords: [ "weather", "summer", "sunny", "sand", "mojito" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd6" src="1f3d6.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + desert_island: { + keywords: [ "photo", "tropical", "mojito" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdd" src="1f3dd.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + city_sunrise: { + keywords: [ "photo", "good morning", "dawn" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf07" src="1f307.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + city_sunset: { + keywords: [ "photo", "evening", "sky", "buildings" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf06" src="1f306.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + cityscape: { + keywords: [ "photo", "night life", "urban" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd9" src="1f3d9.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + night_with_stars: { + keywords: [ "evening", "city", "downtown" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf03" src="1f303.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bridge_at_night: { + keywords: [ "photo", "sanfrancisco" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf09" src="1f309.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + milky_way: { + keywords: [ "photo", "space", "stars" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0c" src="1f30c.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + stars: { + keywords: [ "night", "photo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf20" src="1f320.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sparkler: { + keywords: [ "stars", "night", "shine" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf87" src="1f387.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fireworks: { + keywords: [ "photo", "festival", "carnival", "congratulations" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf86" src="1f386.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rainbow: { + keywords: [ "nature", "happy", "unicorn_face", "photo", "sky", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf08" src="1f308.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + houses: { + keywords: [ "buildings", "photo" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd8" src="1f3d8.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + european_castle: { + keywords: [ "building", "royalty", "history" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff0" src="1f3f0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + japanese_castle: { + keywords: [ "photo", "building" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfef" src="1f3ef.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + stadium: { + keywords: [ "photo", "place", "sports", "concert", "venue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdf" src="1f3df.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + statue_of_liberty: { + keywords: [ "american", "newyork" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfd" src="1f5fd.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + house: { + keywords: [ "building", "home" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe0" src="1f3e0.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + house_with_garden: { + keywords: [ "home", "plant", "nature" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe1" src="1f3e1.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + derelict_house: { + keywords: [ "abandon", "evict", "broken", "building" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfda" src="1f3da.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + office: { + keywords: [ "building", "bureau", "work" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe2" src="1f3e2.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + department_store: { + keywords: [ "building", "shopping", "mall" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfec" src="1f3ec.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + post_office: { + keywords: [ "building", "envelope", "communication" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe3" src="1f3e3.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + european_post_office: { + keywords: [ "building", "email" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe4" src="1f3e4.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + hospital: { + keywords: [ "building", "health", "surgery", "doctor" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe5" src="1f3e5.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bank: { + keywords: [ "building", "money", "sales", "cash", "business", "enterprise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe6" src="1f3e6.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + hotel: { + keywords: [ "building", "accomodation", "checkin" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe8" src="1f3e8.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + convenience_store: { + keywords: [ "building", "shopping", "groceries" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfea" src="1f3ea.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + school: { + keywords: [ "building", "student", "education", "learn", "teach" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfeb" src="1f3eb.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + love_hotel: { + keywords: [ "like", "affection", "dating" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe9" src="1f3e9.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + wedding: { + keywords: [ "love", "like", "affection", "couple", "marriage", "bride", "groom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc92" src="1f492.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + classical_building: { + keywords: [ "art", "culture", "history" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdb" src="1f3db.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + church: { + keywords: [ "building", "religion", "christ" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ea" src="26ea.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mosque: { + keywords: [ "islam", "worship", "minaret" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4c" src="1f54c.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + synagogue: { + keywords: [ "judaism", "worship", "temple", "jewish" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4d" src="1f54d.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + kaaba: { + keywords: [ "mecca", "mosque", "islam" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4b" src="1f54b.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + shinto_shrine: { + keywords: [ "temple", "japan", "kyoto" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26e9" src="26e9.png"/>', + fitzpatrick_scale: false, + category: "travel_and_places" + }, + watch: { + keywords: [ "time", "accessories" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u231a" src="231a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + iphone: { + keywords: [ "technology", "apple", "gadgets", "dial" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf1" src="1f4f1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + calling: { + keywords: [ "iphone", "incoming" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf2" src="1f4f2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + computer: { + keywords: [ "technology", "laptop", "screen", "display", "monitor" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbb" src="1f4bb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + keyboard: { + keywords: [ "technology", "computer", "type", "input", "text" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2328" src="2328.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + desktop_computer: { + keywords: [ "technology", "computing", "screen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda5" src="1f5a5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + printer: { + keywords: [ "paper", "ink" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda8" src="1f5a8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + computer_mouse: { + keywords: [ "click" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddb1" src="1f5b1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + trackball: { + keywords: [ "technology", "trackpad" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddb2" src="1f5b2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + joystick: { + keywords: [ "game", "play" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd79" src="1f579.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + clamp: { + keywords: [ "tool" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udddc" src="1f5dc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + minidisc: { + keywords: [ "technology", "record", "data", "disk", "90s" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbd" src="1f4bd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + floppy_disk: { + keywords: [ "oldschool", "technology", "save", "90s", "80s" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbe" src="1f4be.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + cd: { + keywords: [ "technology", "dvd", "disk", "disc", "90s" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbf" src="1f4bf.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + dvd: { + keywords: [ "cd", "disk", "disc" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc0" src="1f4c0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + vhs: { + keywords: [ "record", "video", "oldschool", "90s", "80s" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfc" src="1f4fc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + camera: { + keywords: [ "gadgets", "photography" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf7" src="1f4f7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + camera_flash: { + keywords: [ "photography", "gadgets" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf8" src="1f4f8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + video_camera: { + keywords: [ "film", "record" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf9" src="1f4f9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + movie_camera: { + keywords: [ "film", "record" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa5" src="1f3a5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + film_projector: { + keywords: [ "video", "tape", "record", "movie" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfd" src="1f4fd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + film_strip: { + keywords: [ "movie" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9e" src="1f39e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + telephone_receiver: { + keywords: [ "technology", "communication", "dial" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcde" src="1f4de.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + phone: { + keywords: [ "technology", "communication", "dial", "telephone" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u260e\ufe0f" src="260e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pager: { + keywords: [ "bbcall", "oldschool", "90s" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdf" src="1f4df.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + fax: { + keywords: [ "communication", "technology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce0" src="1f4e0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + tv: { + keywords: [ "technology", "program", "oldschool", "show", "television" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfa" src="1f4fa.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + radio: { + keywords: [ "communication", "music", "podcast", "program" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfb" src="1f4fb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + studio_microphone: { + keywords: [ "sing", "recording", "artist", "talkshow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf99" src="1f399.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + level_slider: { + keywords: [ "scale" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9a" src="1f39a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + control_knobs: { + keywords: [ "dial" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9b" src="1f39b.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + compass: { + keywords: [ "magnetic", "navigation", "orienteering" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udded" src="1f9ed.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + stopwatch: { + keywords: [ "time", "deadline" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f1" src="23f1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + timer_clock: { + keywords: [ "alarm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f2" src="23f2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + alarm_clock: { + keywords: [ "time", "wake" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f0" src="23f0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mantelpiece_clock: { + keywords: [ "time" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd70" src="1f570.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hourglass_flowing_sand: { + keywords: [ "oldschool", "time", "countdown" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f3" src="23f3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hourglass: { + keywords: [ "time", "clock", "oldschool", "limit", "exam", "quiz", "test" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u231b" src="231b.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + satellite: { + keywords: [ "communication", "future", "radio", "space" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce1" src="1f4e1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + battery: { + keywords: [ "power", "energy", "sustain" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0b" src="1f50b.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + electric_plug: { + keywords: [ "charger", "power" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0c" src="1f50c.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bulb: { + keywords: [ "light", "electricity", "idea" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca1" src="1f4a1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + flashlight: { + keywords: [ "dark", "camping", "sight", "night" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd26" src="1f526.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + candle: { + keywords: [ "fire", "wax" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd6f" src="1f56f.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + fire_extinguisher: { + keywords: [ "quench" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddef" src="1f9ef.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + wastebasket: { + keywords: [ "bin", "trash", "rubbish", "garbage", "toss" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd1" src="1f5d1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + oil_drum: { + keywords: [ "barrell" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee2" src="1f6e2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + money_with_wings: { + keywords: [ "dollar", "bills", "payment", "sale" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb8" src="1f4b8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + dollar: { + keywords: [ "money", "sales", "bill", "currency" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb5" src="1f4b5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + yen: { + keywords: [ "money", "sales", "japanese", "dollar", "currency" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb4" src="1f4b4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + euro: { + keywords: [ "money", "sales", "dollar", "currency" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb6" src="1f4b6.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pound: { + keywords: [ "british", "sterling", "money", "sales", "bills", "uk", "england", "currency" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb7" src="1f4b7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + moneybag: { + keywords: [ "dollar", "payment", "coins", "sale" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb0" src="1f4b0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + credit_card: { + keywords: [ "money", "sales", "dollar", "bill", "payment", "shopping" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb3" src="1f4b3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + gem: { + keywords: [ "blue", "ruby", "diamond", "jewelry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8e" src="1f48e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + balance_scale: { + keywords: [ "law", "fairness", "weight" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2696" src="2696.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + toolbox: { + keywords: [ "tools", "diy", "fix", "maintainer", "mechanic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf0" src="1f9f0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + wrench: { + keywords: [ "tools", "diy", "ikea", "fix", "maintainer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd27" src="1f527.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hammer: { + keywords: [ "tools", "build", "create" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd28" src="1f528.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hammer_and_pick: { + keywords: [ "tools", "build", "create" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2692" src="2692.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hammer_and_wrench: { + keywords: [ "tools", "build", "create" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee0" src="1f6e0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pick: { + keywords: [ "tools", "dig" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26cf" src="26cf.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + nut_and_bolt: { + keywords: [ "handy", "tools", "fix" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd29" src="1f529.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + gear: { + keywords: [ "cog" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2699" src="2699.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + brick: { + keywords: [ "bricks" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf1" src="1f9f1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + chains: { + keywords: [ "lock", "arrest" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d3" src="26d3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + magnet: { + keywords: [ "attraction", "magnetic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf2" src="1f9f2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + gun: { + keywords: [ "violence", "weapon", "pistol", "revolver" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2b" src="1f52b.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bomb: { + keywords: [ "boom", "explode", "explosion", "terrorism" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca3" src="1f4a3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + firecracker: { + keywords: [ "dynamite", "boom", "explode", "explosion", "explosive" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde8" src="1f9e8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hocho: { + keywords: [ "knife", "blade", "cutlery", "kitchen", "weapon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2a" src="1f52a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + dagger: { + keywords: [ "weapon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde1" src="1f5e1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + crossed_swords: { + keywords: [ "weapon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2694" src="2694.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + shield: { + keywords: [ "protection", "security" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee1" src="1f6e1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + smoking: { + keywords: [ "kills", "tobacco", "cigarette", "joint", "smoke" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeac" src="1f6ac.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + skull_and_crossbones: { + keywords: [ "poison", "danger", "deadly", "scary", "death", "pirate", "evil" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2620" src="2620.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + coffin: { + keywords: [ "vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26b0" src="26b0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + funeral_urn: { + keywords: [ "dead", "die", "death", "rip", "ashes" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26b1" src="26b1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + amphora: { + keywords: [ "vase", "jar" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udffa" src="1f3fa.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + crystal_ball: { + keywords: [ "disco", "party", "magic", "circus", "fortune_teller" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2e" src="1f52e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + prayer_beads: { + keywords: [ "dhikr", "religious" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcff" src="1f4ff.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + nazar_amulet: { + keywords: [ "bead", "charm" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddff" src="1f9ff.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + barber: { + keywords: [ "hair", "salon", "style" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc88" src="1f488.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + alembic: { + keywords: [ "distilling", "science", "experiment", "chemistry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2697" src="2697.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + telescope: { + keywords: [ "stars", "space", "zoom", "science", "astronomy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2d" src="1f52d.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + microscope: { + keywords: [ "laboratory", "experiment", "zoomin", "science", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2c" src="1f52c.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + hole: { + keywords: [ "embarrassing" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd73" src="1f573.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pill: { + keywords: [ "health", "medicine", "doctor", "pharmacy", "drug" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8a" src="1f48a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + syringe: { + keywords: [ "health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc89" src="1f489.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + dna: { + keywords: [ "biologist", "genetics", "life" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddec" src="1f9ec.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + microbe: { + keywords: [ "amoeba", "bacteria", "germs" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda0" src="1f9a0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + petri_dish: { + keywords: [ "bacteria", "biology", "culture", "lab" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddeb" src="1f9eb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + test_tube: { + keywords: [ "chemistry", "experiment", "lab", "science" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddea" src="1f9ea.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + thermometer: { + keywords: [ "weather", "temperature", "hot", "cold" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf21" src="1f321.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + broom: { + keywords: [ "cleaning", "sweeping", "witch" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf9" src="1f9f9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + basket: { + keywords: [ "laundry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfa" src="1f9fa.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + toilet_paper: { + keywords: [ "roll" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfb" src="1f9fb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + label: { + keywords: [ "sale", "tag" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff7" src="1f3f7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bookmark: { + keywords: [ "favorite", "label", "save" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd16" src="1f516.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + toilet: { + keywords: [ "restroom", "wc", "washroom", "bathroom", "potty" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebd" src="1f6bd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + shower: { + keywords: [ "clean", "water", "bathroom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebf" src="1f6bf.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bathtub: { + keywords: [ "clean", "shower", "bathroom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec1" src="1f6c1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + soap: { + keywords: [ "bar", "bathing", "cleaning", "lather" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfc" src="1f9fc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + sponge: { + keywords: [ "absorbing", "cleaning", "porous" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfd" src="1f9fd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + lotion_bottle: { + keywords: [ "moisturizer", "sunscreen" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf4" src="1f9f4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + key: { + keywords: [ "lock", "door", "password" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd11" src="1f511.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + old_key: { + keywords: [ "lock", "door", "password" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udddd" src="1f5dd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + couch_and_lamp: { + keywords: [ "read", "chill" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecb" src="1f6cb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + sleeping_bed: { + keywords: [ "bed", "rest" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecc" src="1f6cc.png"/>', + fitzpatrick_scale: true, + category: "objects" + }, + bed: { + keywords: [ "sleep", "rest" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecf" src="1f6cf.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + door: { + keywords: [ "house", "entry", "exit" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeaa" src="1f6aa.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bellhop_bell: { + keywords: [ "service" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udece" src="1f6ce.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + teddy_bear: { + keywords: [ "plush", "stuffed" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf8" src="1f9f8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + framed_picture: { + keywords: [ "photography" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddbc" src="1f5bc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + world_map: { + keywords: [ "location", "direction" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfa" src="1f5fa.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + parasol_on_ground: { + keywords: [ "weather", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f1" src="26f1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + moyai: { + keywords: [ "rock", "easter island", "moai" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddff" src="1f5ff.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + shopping: { + keywords: [ "mall", "buy", "purchase" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecd" src="1f6cd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + shopping_cart: { + keywords: [ "trolley" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded2" src="1f6d2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + balloon: { + keywords: [ "party", "celebration", "birthday", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf88" src="1f388.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + flags: { + keywords: [ "fish", "japanese", "koinobori", "carp", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8f" src="1f38f.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + ribbon: { + keywords: [ "decoration", "pink", "girl", "bowtie" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf80" src="1f380.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + gift: { + keywords: [ "present", "birthday", "christmas", "xmas" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf81" src="1f381.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + confetti_ball: { + keywords: [ "festival", "party", "birthday", "circus" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8a" src="1f38a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + tada: { + keywords: [ "party", "congratulations", "birthday", "magic", "circus", "celebration" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf89" src="1f389.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + dolls: { + keywords: [ "japanese", "toy", "kimono" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8e" src="1f38e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + wind_chime: { + keywords: [ "nature", "ding", "spring", "bell" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf90" src="1f390.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + crossed_flags: { + keywords: [ "japanese", "nation", "country", "border" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8c" src="1f38c.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + izakaya_lantern: { + keywords: [ "light", "paper", "halloween", "spooky" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfee" src="1f3ee.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + red_envelope: { + keywords: [ "gift" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde7" src="1f9e7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + email: { + keywords: [ "letter", "postal", "inbox", "communication" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2709\ufe0f" src="2709.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + envelope_with_arrow: { + keywords: [ "email", "communication" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce9" src="1f4e9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + incoming_envelope: { + keywords: [ "email", "inbox" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce8" src="1f4e8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + "e-mail": { + keywords: [ "communication", "inbox" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce7" src="1f4e7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + love_letter: { + keywords: [ "email", "like", "affection", "envelope", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8c" src="1f48c.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + postbox: { + keywords: [ "email", "letter", "envelope" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcee" src="1f4ee.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_closed: { + keywords: [ "email", "communication", "inbox" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcea" src="1f4ea.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mailbox: { + keywords: [ "email", "inbox", "communication" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udceb" src="1f4eb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_with_mail: { + keywords: [ "email", "inbox", "communication" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcec" src="1f4ec.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_with_no_mail: { + keywords: [ "email", "inbox" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udced" src="1f4ed.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + package: { + keywords: [ "mail", "gift", "cardboard", "box", "moving" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce6" src="1f4e6.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + postal_horn: { + keywords: [ "instrument", "music" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcef" src="1f4ef.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + inbox_tray: { + keywords: [ "email", "documents" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce5" src="1f4e5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + outbox_tray: { + keywords: [ "inbox", "email" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce4" src="1f4e4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + scroll: { + keywords: [ "documents", "ancient", "history", "paper" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdc" src="1f4dc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + page_with_curl: { + keywords: [ "documents", "office", "paper" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc3" src="1f4c3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bookmark_tabs: { + keywords: [ "favorite", "save", "order", "tidy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd1" src="1f4d1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + receipt: { + keywords: [ "accounting", "expenses" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfe" src="1f9fe.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + bar_chart: { + keywords: [ "graph", "presentation", "stats" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcca" src="1f4ca.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + chart_with_upwards_trend: { + keywords: [ "graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc8" src="1f4c8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + chart_with_downwards_trend: { + keywords: [ "graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc9" src="1f4c9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + page_facing_up: { + keywords: [ "documents", "office", "paper", "information" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc4" src="1f4c4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + date: { + keywords: [ "calendar", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc5" src="1f4c5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + calendar: { + keywords: [ "schedule", "date", "planning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc6" src="1f4c6.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + spiral_calendar: { + keywords: [ "date", "schedule", "planning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd3" src="1f5d3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + card_index: { + keywords: [ "business", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc7" src="1f4c7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + card_file_box: { + keywords: [ "business", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc3" src="1f5c3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + ballot_box: { + keywords: [ "election", "vote" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddf3" src="1f5f3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + file_cabinet: { + keywords: [ "filing", "organizing" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc4" src="1f5c4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + clipboard: { + keywords: [ "stationery", "documents" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccb" src="1f4cb.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + spiral_notepad: { + keywords: [ "memo", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd2" src="1f5d2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + file_folder: { + keywords: [ "documents", "business", "office" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc1" src="1f4c1.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + open_file_folder: { + keywords: [ "documents", "load" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc2" src="1f4c2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + card_index_dividers: { + keywords: [ "organizing", "business", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc2" src="1f5c2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + newspaper_roll: { + keywords: [ "press", "headline" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddde" src="1f5de.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + newspaper: { + keywords: [ "press", "headline" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf0" src="1f4f0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + notebook: { + keywords: [ "stationery", "record", "notes", "paper", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd3" src="1f4d3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + closed_book: { + keywords: [ "read", "library", "knowledge", "textbook", "learn" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd5" src="1f4d5.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + green_book: { + keywords: [ "read", "library", "knowledge", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd7" src="1f4d7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + blue_book: { + keywords: [ "read", "library", "knowledge", "learn", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd8" src="1f4d8.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + orange_book: { + keywords: [ "read", "library", "knowledge", "textbook", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd9" src="1f4d9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + notebook_with_decorative_cover: { + keywords: [ "classroom", "notes", "record", "paper", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd4" src="1f4d4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + ledger: { + keywords: [ "notes", "paper" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd2" src="1f4d2.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + books: { + keywords: [ "literature", "library", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcda" src="1f4da.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + open_book: { + keywords: [ "book", "read", "library", "knowledge", "literature", "learn", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd6" src="1f4d6.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + safety_pin: { + keywords: [ "diaper" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf7" src="1f9f7.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + link: { + keywords: [ "rings", "url" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd17" src="1f517.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + paperclip: { + keywords: [ "documents", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcce" src="1f4ce.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + paperclips: { + keywords: [ "documents", "stationery" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd87" src="1f587.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + scissors: { + keywords: [ "stationery", "cut" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2702\ufe0f" src="2702.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + triangular_ruler: { + keywords: [ "stationery", "math", "architect", "sketch" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd0" src="1f4d0.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + straight_ruler: { + keywords: [ "stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccf" src="1f4cf.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + abacus: { + keywords: [ "calculation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddee" src="1f9ee.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pushpin: { + keywords: [ "stationery", "mark", "here" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccc" src="1f4cc.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + round_pushpin: { + keywords: [ "stationery", "location", "map", "here" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccd" src="1f4cd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + triangular_flag_on_post: { + keywords: [ "mark", "milestone", "place" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea9" src="1f6a9.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + white_flag: { + keywords: [ "losing", "loser", "lost", "surrender", "give up", "fail" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff3" src="1f3f3.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + black_flag: { + keywords: [ "pirate" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4" src="1f3f4.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + rainbow_flag: { + keywords: [ "flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff3\ufe0f\u200d\ud83c\udf08" src="1f3f3-fe0f-200d-1f308.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + closed_lock_with_key: { + keywords: [ "security", "privacy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd10" src="1f510.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + lock: { + keywords: [ "security", "password", "padlock" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd12" src="1f512.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + unlock: { + keywords: [ "privacy", "security" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd13" src="1f513.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + lock_with_ink_pen: { + keywords: [ "security", "secret" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0f" src="1f50f.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pen: { + keywords: [ "stationery", "writing", "write" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8a" src="1f58a.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + fountain_pen: { + keywords: [ "stationery", "writing", "write" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8b" src="1f58b.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + black_nib: { + keywords: [ "pen", "stationery", "writing", "write" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2712\ufe0f" src="2712.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + memo: { + keywords: [ "write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdd" src="1f4dd.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + pencil2: { + keywords: [ "stationery", "write", "paper", "writing", "school", "study" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270f\ufe0f" src="270f.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + crayon: { + keywords: [ "drawing", "creativity" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8d" src="1f58d.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + paintbrush: { + keywords: [ "drawing", "creativity", "art" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8c" src="1f58c.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mag: { + keywords: [ "search", "zoom", "find", "detective" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0d" src="1f50d.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + mag_right: { + keywords: [ "search", "zoom", "find", "detective" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0e" src="1f50e.png"/>', + fitzpatrick_scale: false, + category: "objects" + }, + heart: { + keywords: [ "love", "like", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2764\ufe0f" src="2764.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + orange_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde1" src="1f9e1.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + yellow_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9b" src="1f49b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + green_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9a" src="1f49a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + blue_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc99" src="1f499.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + purple_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9c" src="1f49c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_heart: { + keywords: [ "evil" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda4" src="1f5a4.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + broken_heart: { + keywords: [ "sad", "sorry", "break", "heart", "heartbreak" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc94" src="1f494.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_heart_exclamation: { + keywords: [ "decoration", "love" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2763" src="2763.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + two_hearts: { + keywords: [ "love", "like", "affection", "valentines", "heart" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc95" src="1f495.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + revolving_hearts: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9e" src="1f49e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heartbeat: { + keywords: [ "love", "like", "affection", "valentines", "pink", "heart" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc93" src="1f493.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heartpulse: { + keywords: [ "like", "love", "affection", "valentines", "pink" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc97" src="1f497.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sparkling_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc96" src="1f496.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cupid: { + keywords: [ "love", "like", "heart", "affection", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc98" src="1f498.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + gift_heart: { + keywords: [ "love", "valentines" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9d" src="1f49d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heart_decoration: { + keywords: [ "purple-square", "love", "like" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9f" src="1f49f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + peace_symbol: { + keywords: [ "hippie" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262e" src="262e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + latin_cross: { + keywords: [ "christianity" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u271d" src="271d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + star_and_crescent: { + keywords: [ "islam" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262a" src="262a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + om: { + keywords: [ "hinduism", "buddhism", "sikhism", "jainism" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd49" src="1f549.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + wheel_of_dharma: { + keywords: [ "hinduism", "buddhism", "sikhism", "jainism" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2638" src="2638.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + star_of_david: { + keywords: [ "judaism" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2721" src="2721.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + six_pointed_star: { + keywords: [ "purple-square", "religion", "jewish", "hexagram" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2f" src="1f52f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + menorah: { + keywords: [ "hanukkah", "candles", "jewish" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4e" src="1f54e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + yin_yang: { + keywords: [ "balance" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262f" src="262f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + orthodox_cross: { + keywords: [ "suppedaneum", "religion" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2626" src="2626.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + place_of_worship: { + keywords: [ "religion", "church", "temple", "prayer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded0" src="1f6d0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ophiuchus: { + keywords: [ "sign", "purple-square", "constellation", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ce" src="26ce.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + aries: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2648" src="2648.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + taurus: { + keywords: [ "purple-square", "sign", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2649" src="2649.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + gemini: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264a" src="264a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cancer: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264b" src="264b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + leo: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264c" src="264c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + virgo: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264d" src="264d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + libra: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264e" src="264e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + scorpius: { + keywords: [ "sign", "zodiac", "purple-square", "astrology", "scorpio" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264f" src="264f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sagittarius: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2650" src="2650.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + capricorn: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2651" src="2651.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + aquarius: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2652" src="2652.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + pisces: { + keywords: [ "purple-square", "sign", "zodiac", "astrology" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2653" src="2653.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + id: { + keywords: [ "purple-square", "words" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd94" src="1f194.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + atom_symbol: { + keywords: [ "science", "physics", "chemistry" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u269b" src="269b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u7a7a: { + keywords: [ "kanji", "japanese", "chinese", "empty", "sky", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude33" src="1f233.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u5272: { + keywords: [ "cut", "divide", "chinese", "kanji", "pink-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude39" src="1f239.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + radioactive: { + keywords: [ "nuclear", "danger" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2622" src="2622.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + biohazard: { + keywords: [ "danger" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2623" src="2623.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + mobile_phone_off: { + keywords: [ "mute", "orange-square", "silence", "quiet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf4" src="1f4f4.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + vibration_mode: { + keywords: [ "orange-square", "phone" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf3" src="1f4f3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u6709: { + keywords: [ "orange-square", "chinese", "have", "kanji" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude36" src="1f236.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u7121: { + keywords: [ "nothing", "chinese", "kanji", "japanese", "orange-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude1a" src="1f21a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u7533: { + keywords: [ "chinese", "japanese", "kanji", "orange-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude38" src="1f238.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u55b6: { + keywords: [ "japanese", "opening hours", "orange-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude3a" src="1f23a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u6708: { + keywords: [ "chinese", "month", "moon", "japanese", "orange-square", "kanji" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude37\ufe0f" src="1f237.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + eight_pointed_black_star: { + keywords: [ "orange-square", "shape", "polygon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2734\ufe0f" src="2734.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + vs: { + keywords: [ "words", "orange-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd9a" src="1f19a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + accept: { + keywords: [ "ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude51" src="1f251.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_flower: { + keywords: [ "japanese", "spring" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcae" src="1f4ae.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ideograph_advantage: { + keywords: [ "chinese", "kanji", "obtain", "get", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude50" src="1f250.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + secret: { + keywords: [ "privacy", "chinese", "sshh", "kanji", "red-circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3299\ufe0f" src="3299.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + congratulations: { + keywords: [ "chinese", "kanji", "japanese", "red-circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3297\ufe0f" src="3297.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u5408: { + keywords: [ "japanese", "chinese", "join", "kanji", "red-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude34" src="1f234.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u6e80: { + keywords: [ "full", "chinese", "japanese", "red-square", "kanji" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude35" src="1f235.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u7981: { + keywords: [ "kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude32" src="1f232.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + a: { + keywords: [ "red-square", "alphabet", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd70\ufe0f" src="1f170.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + b: { + keywords: [ "red-square", "alphabet", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd71\ufe0f" src="1f171.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ab: { + keywords: [ "red-square", "alphabet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd8e" src="1f18e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cl: { + keywords: [ "alphabet", "words", "red-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd91" src="1f191.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + o2: { + keywords: [ "alphabet", "red-square", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd7e\ufe0f" src="1f17e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sos: { + keywords: [ "help", "red-square", "words", "emergency", "911" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd98" src="1f198.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_entry: { + keywords: [ "limit", "security", "privacy", "bad", "denied", "stop", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d4" src="26d4.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + name_badge: { + keywords: [ "fire", "forbid" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdb" src="1f4db.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_entry_sign: { + keywords: [ "forbid", "stop", "limit", "denied", "disallow", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeab" src="1f6ab.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + x: { + keywords: [ "no", "delete", "remove", "cancel", "red" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u274c" src="274c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + o: { + keywords: [ "circle", "round" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b55" src="2b55.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + stop_sign: { + keywords: [ "stop" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded1" src="1f6d1.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + anger: { + keywords: [ "angry", "mad" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca2" src="1f4a2.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + hotsprings: { + keywords: [ "bath", "warm", "relax" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2668\ufe0f" src="2668.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_pedestrians: { + keywords: [ "rules", "crossing", "walking", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb7" src="1f6b7.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + do_not_litter: { + keywords: [ "trash", "bin", "garbage", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeaf" src="1f6af.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_bicycles: { + keywords: [ "cyclist", "prohibited", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb3" src="1f6b3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + "non-potable_water": { + keywords: [ "drink", "faucet", "tap", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb1" src="1f6b1.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + underage: { + keywords: [ "18", "drink", "pub", "night", "minor", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1e" src="1f51e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_mobile_phones: { + keywords: [ "iphone", "mute", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf5" src="1f4f5.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + exclamation: { + keywords: [ "heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2757" src="2757.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + grey_exclamation: { + keywords: [ "surprise", "punctuation", "gray", "wow", "warning" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2755" src="2755.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + question: { + keywords: [ "doubt", "confused" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2753" src="2753.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + grey_question: { + keywords: [ "doubts", "gray", "huh", "confused" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2754" src="2754.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + bangbang: { + keywords: [ "exclamation", "surprise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u203c\ufe0f" src="203c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + interrobang: { + keywords: [ "wat", "punctuation", "surprise" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2049\ufe0f" src="2049.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + low_brightness: { + keywords: [ "sun", "afternoon", "warm", "summer" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd05" src="1f505.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + high_brightness: { + keywords: [ "sun", "light" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd06" src="1f506.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + trident: { + keywords: [ "weapon", "spear" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd31" src="1f531.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + fleur_de_lis: { + keywords: [ "decorative", "scout" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u269c" src="269c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + part_alternation_mark: { + keywords: [ "graph", "presentation", "stats", "business", "economics", "bad" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u303d\ufe0f" src="303d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + warning: { + keywords: [ "exclamation", "wip", "alert", "error", "problem", "issue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26a0\ufe0f" src="26a0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + children_crossing: { + keywords: [ "school", "warning", "danger", "sign", "driving", "yellow-diamond" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb8" src="1f6b8.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + beginner: { + keywords: [ "badge", "shield" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd30" src="1f530.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + recycle: { + keywords: [ "arrow", "environment", "garbage", "trash" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267b\ufe0f" src="267b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + u6307: { + keywords: [ "chinese", "point", "green-square", "kanji" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude2f" src="1f22f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + chart: { + keywords: [ "green-square", "graph", "presentation", "stats" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb9" src="1f4b9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sparkle: { + keywords: [ "stars", "green-square", "awesome", "good", "fireworks" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2747\ufe0f" src="2747.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + eight_spoked_asterisk: { + keywords: [ "star", "sparkle", "green-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2733\ufe0f" src="2733.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + negative_squared_cross_mark: { + keywords: [ "x", "green-square", "no", "deny" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u274e" src="274e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_check_mark: { + keywords: [ "green-square", "ok", "agree", "vote", "election", "answer", "tick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2705" src="2705.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + diamond_shape_with_a_dot_inside: { + keywords: [ "jewel", "blue", "gem", "crystal", "fancy" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca0" src="1f4a0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cyclone: { + keywords: [ "weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf00" src="1f300.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + loop: { + keywords: [ "tape", "cassette" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27bf" src="27bf.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + globe_with_meridians: { + keywords: [ "earth", "international", "world", "internet", "interweb", "i18n" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf10" src="1f310.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + m: { + keywords: [ "alphabet", "blue-circle", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u24c2\ufe0f" src="24c2.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + atm: { + keywords: [ "money", "sales", "cash", "blue-square", "payment", "bank" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe7" src="1f3e7.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sa: { + keywords: [ "japanese", "blue-square", "katakana" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude02\ufe0f" src="1f202.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + passport_control: { + keywords: [ "custom", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec2" src="1f6c2.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + customs: { + keywords: [ "passport", "border", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec3" src="1f6c3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + baggage_claim: { + keywords: [ "blue-square", "airport", "transport" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec4" src="1f6c4.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + left_luggage: { + keywords: [ "blue-square", "travel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec5" src="1f6c5.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + wheelchair: { + keywords: [ "blue-square", "disabled", "a11y", "accessibility" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267f" src="267f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_smoking: { + keywords: [ "cigarette", "blue-square", "smell", "smoke" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udead" src="1f6ad.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + wc: { + keywords: [ "toilet", "restroom", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebe" src="1f6be.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + parking: { + keywords: [ "cars", "blue-square", "alphabet", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd7f\ufe0f" src="1f17f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + potable_water: { + keywords: [ "blue-square", "liquid", "restroom", "cleaning", "faucet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb0" src="1f6b0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + mens: { + keywords: [ "toilet", "restroom", "wc", "blue-square", "gender", "male" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb9" src="1f6b9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + womens: { + keywords: [ "purple-square", "woman", "female", "toilet", "loo", "restroom", "gender" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeba" src="1f6ba.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + baby_symbol: { + keywords: [ "orange-square", "child" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebc" src="1f6bc.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + restroom: { + keywords: [ "blue-square", "toilet", "refresh", "wc", "gender" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebb" src="1f6bb.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + put_litter_in_its_place: { + keywords: [ "blue-square", "sign", "human", "info" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeae" src="1f6ae.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cinema: { + keywords: [ "blue-square", "record", "film", "movie", "curtain", "stage", "theater" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa6" src="1f3a6.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + signal_strength: { + keywords: [ "blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf6" src="1f4f6.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + koko: { + keywords: [ "blue-square", "here", "katakana", "japanese", "destination" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude01" src="1f201.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ng: { + keywords: [ "blue-square", "words", "shape", "icon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd96" src="1f196.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ok: { + keywords: [ "good", "agree", "yes", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd97" src="1f197.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + up: { + keywords: [ "blue-square", "above", "high" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd99" src="1f199.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + cool: { + keywords: [ "words", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd92" src="1f192.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + new: { + keywords: [ "blue-square", "words", "start" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd95" src="1f195.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + free: { + keywords: [ "blue-square", "words" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd93" src="1f193.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + zero: { + keywords: [ "0", "numbers", "blue-square", "null" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="0\ufe0f\u20e3" src="30-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + one: { + keywords: [ "blue-square", "numbers", "1" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="1\ufe0f\u20e3" src="31-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + two: { + keywords: [ "numbers", "2", "prime", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="2\ufe0f\u20e3" src="32-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + three: { + keywords: [ "3", "numbers", "prime", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="3\ufe0f\u20e3" src="33-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + four: { + keywords: [ "4", "numbers", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="4\ufe0f\u20e3" src="34-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + five: { + keywords: [ "5", "numbers", "blue-square", "prime" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="5\ufe0f\u20e3" src="35-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + six: { + keywords: [ "6", "numbers", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="6\ufe0f\u20e3" src="36-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + seven: { + keywords: [ "7", "numbers", "blue-square", "prime" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="7\ufe0f\u20e3" src="37-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + eight: { + keywords: [ "8", "blue-square", "numbers" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="8\ufe0f\u20e3" src="38-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + nine: { + keywords: [ "blue-square", "numbers", "9" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="9\ufe0f\u20e3" src="39-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + keycap_ten: { + keywords: [ "numbers", "10", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1f" src="1f51f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + asterisk: { + keywords: [ "star", "keycap" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="*\u20e3" src="2a-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + eject_button: { + keywords: [ "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23cf\ufe0f" src="23cf.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_forward: { + keywords: [ "blue-square", "right", "direction", "play" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25b6\ufe0f" src="25b6.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + pause_button: { + keywords: [ "pause", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f8" src="23f8.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + next_track_button: { + keywords: [ "forward", "next", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ed" src="23ed.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + stop_button: { + keywords: [ "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f9" src="23f9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + record_button: { + keywords: [ "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23fa" src="23fa.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + play_or_pause_button: { + keywords: [ "blue-square", "play", "pause" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ef" src="23ef.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + previous_track_button: { + keywords: [ "backward" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ee" src="23ee.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + fast_forward: { + keywords: [ "blue-square", "play", "speed", "continue" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23e9" src="23e9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + rewind: { + keywords: [ "play", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ea" src="23ea.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + twisted_rightwards_arrows: { + keywords: [ "blue-square", "shuffle", "music", "random" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd00" src="1f500.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + repeat: { + keywords: [ "loop", "record" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd01" src="1f501.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + repeat_one: { + keywords: [ "blue-square", "loop" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd02" src="1f502.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_backward: { + keywords: [ "blue-square", "left", "direction" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25c0\ufe0f" src="25c0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up_small: { + keywords: [ "blue-square", "triangle", "direction", "point", "forward", "top" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3c" src="1f53c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_down_small: { + keywords: [ "blue-square", "direction", "bottom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3d" src="1f53d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_double_up: { + keywords: [ "blue-square", "direction", "top" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23eb" src="23eb.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_double_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ec" src="23ec.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_right: { + keywords: [ "blue-square", "next" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27a1\ufe0f" src="27a1.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_left: { + keywords: [ "blue-square", "previous", "back" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b05\ufe0f" src="2b05.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up: { + keywords: [ "blue-square", "continue", "top", "direction" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b06\ufe0f" src="2b06.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b07\ufe0f" src="2b07.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_upper_right: { + keywords: [ "blue-square", "point", "direction", "diagonal", "northeast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2197\ufe0f" src="2197.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_lower_right: { + keywords: [ "blue-square", "direction", "diagonal", "southeast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2198\ufe0f" src="2198.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_lower_left: { + keywords: [ "blue-square", "direction", "diagonal", "southwest" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2199\ufe0f" src="2199.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_upper_left: { + keywords: [ "blue-square", "point", "direction", "diagonal", "northwest" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2196\ufe0f" src="2196.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up_down: { + keywords: [ "blue-square", "direction", "way", "vertical" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2195\ufe0f" src="2195.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + left_right_arrow: { + keywords: [ "shape", "direction", "horizontal", "sideways" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2194\ufe0f" src="2194.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrows_counterclockwise: { + keywords: [ "blue-square", "sync", "cycle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd04" src="1f504.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_right_hook: { + keywords: [ "blue-square", "return", "rotate", "direction" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u21aa\ufe0f" src="21aa.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + leftwards_arrow_with_hook: { + keywords: [ "back", "return", "blue-square", "undo", "enter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u21a9\ufe0f" src="21a9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_heading_up: { + keywords: [ "blue-square", "direction", "top" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2934\ufe0f" src="2934.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_heading_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2935\ufe0f" src="2935.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + hash: { + keywords: [ "symbol", "blue-square", "twitter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="#\ufe0f\u20e3" src="23-20e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + information_source: { + keywords: [ "blue-square", "alphabet", "letter" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2139\ufe0f" src="2139.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + abc: { + keywords: [ "blue-square", "alphabet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd24" src="1f524.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + abcd: { + keywords: [ "blue-square", "alphabet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd21" src="1f521.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + capital_abcd: { + keywords: [ "alphabet", "words", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd20" src="1f520.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + symbols: { + keywords: [ "blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd23" src="1f523.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + musical_note: { + keywords: [ "score", "tone", "sound" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb5" src="1f3b5.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + notes: { + keywords: [ "music", "score" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb6" src="1f3b6.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + wavy_dash: { + keywords: [ "draw", "line", "moustache", "mustache", "squiggle", "scribble" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3030\ufe0f" src="3030.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + curly_loop: { + keywords: [ "scribble", "draw", "shape", "squiggle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27b0" src="27b0.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_check_mark: { + keywords: [ "ok", "nike", "answer", "yes", "tick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2714\ufe0f" src="2714.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + arrows_clockwise: { + keywords: [ "sync", "cycle", "round", "repeat" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd03" src="1f503.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_plus_sign: { + keywords: [ "math", "calculation", "addition", "more", "increase" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2795" src="2795.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_minus_sign: { + keywords: [ "math", "calculation", "subtract", "less" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2796" src="2796.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_division_sign: { + keywords: [ "divide", "math", "calculation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2797" src="2797.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_multiplication_x: { + keywords: [ "math", "calculation" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2716\ufe0f" src="2716.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + infinity: { + keywords: [ "forever" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267e" src="267e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_dollar_sign: { + keywords: [ "money", "sales", "payment", "currency", "buck" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb2" src="1f4b2.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + currency_exchange: { + keywords: [ "money", "sales", "dollar", "travel" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb1" src="1f4b1.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + copyright: { + keywords: [ "ip", "license", "circle", "law", "legal" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\xa9\ufe0f" src="a9.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + registered: { + keywords: [ "alphabet", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\xae\ufe0f" src="ae.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + tm: { + keywords: [ "trademark", "brand", "law", "legal" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2122\ufe0f" src="2122.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + end: { + keywords: [ "words", "arrow" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1a" src="1f51a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + back: { + keywords: [ "arrow", "words", "return" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd19" src="1f519.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + on: { + keywords: [ "arrow", "words" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1b" src="1f51b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + top: { + keywords: [ "words", "blue-square" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1d" src="1f51d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + soon: { + keywords: [ "arrow", "words" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1c" src="1f51c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + ballot_box_with_check: { + keywords: [ "ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2611\ufe0f" src="2611.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + radio_button: { + keywords: [ "input", "old", "music", "circle" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd18" src="1f518.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_circle: { + keywords: [ "shape", "round" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26aa" src="26aa.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_circle: { + keywords: [ "shape", "button", "round" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ab" src="26ab.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + red_circle: { + keywords: [ "shape", "error", "danger" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd34" src="1f534.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + large_blue_circle: { + keywords: [ "shape", "icon", "button" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd35" src="1f535.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + small_orange_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd38" src="1f538.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + small_blue_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd39" src="1f539.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + large_orange_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd36" src="1f536.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + large_blue_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd37" src="1f537.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + small_red_triangle: { + keywords: [ "shape", "direction", "up", "top" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3a" src="1f53a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_small_square: { + keywords: [ "shape", "icon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25aa\ufe0f" src="25aa.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_small_square: { + keywords: [ "shape", "icon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25ab\ufe0f" src="25ab.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_large_square: { + keywords: [ "shape", "icon", "button" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b1b" src="2b1b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_large_square: { + keywords: [ "shape", "icon", "stone", "button" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b1c" src="2b1c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + small_red_triangle_down: { + keywords: [ "shape", "direction", "bottom" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3b" src="1f53b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_medium_square: { + keywords: [ "shape", "button", "icon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fc\ufe0f" src="25fc.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_medium_square: { + keywords: [ "shape", "stone", "icon" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fb\ufe0f" src="25fb.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_medium_small_square: { + keywords: [ "icon", "shape", "button" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fe" src="25fe.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_medium_small_square: { + keywords: [ "shape", "stone", "icon", "button" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fd" src="25fd.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_square_button: { + keywords: [ "shape", "input", "frame" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd32" src="1f532.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + white_square_button: { + keywords: [ "shape", "input" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd33" src="1f533.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + speaker: { + keywords: [ "sound", "volume", "silence", "broadcast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd08" src="1f508.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + sound: { + keywords: [ "volume", "speaker", "broadcast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd09" src="1f509.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + loud_sound: { + keywords: [ "volume", "noise", "noisy", "speaker", "broadcast" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0a" src="1f50a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + mute: { + keywords: [ "sound", "volume", "silence", "quiet" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd07" src="1f507.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + mega: { + keywords: [ "sound", "speaker", "volume" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce3" src="1f4e3.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + loudspeaker: { + keywords: [ "volume", "sound" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce2" src="1f4e2.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + bell: { + keywords: [ "sound", "notification", "christmas", "xmas", "chime" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd14" src="1f514.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + no_bell: { + keywords: [ "sound", "volume", "mute", "quiet", "silent" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd15" src="1f515.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + black_joker: { + keywords: [ "poker", "cards", "game", "play", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udccf" src="1f0cf.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + mahjong: { + keywords: [ "game", "play", "chinese", "kanji" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udc04" src="1f004.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + spades: { + keywords: [ "poker", "cards", "suits", "magic" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2660\ufe0f" src="2660.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clubs: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2663\ufe0f" src="2663.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + hearts: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2665\ufe0f" src="2665.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + diamonds: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2666\ufe0f" src="2666.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + flower_playing_cards: { + keywords: [ "game", "sunset", "red" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb4" src="1f3b4.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + thought_balloon: { + keywords: [ "bubble", "cloud", "speech", "thinking", "dream" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcad" src="1f4ad.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + right_anger_bubble: { + keywords: [ "caption", "speech", "thinking", "mad" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddef" src="1f5ef.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + speech_balloon: { + keywords: [ "bubble", "words", "message", "talk", "chatting" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcac" src="1f4ac.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + left_speech_bubble: { + keywords: [ "words", "message", "talk", "chatting" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde8" src="1f5e8.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock1: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd50" src="1f550.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock2: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd51" src="1f551.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock3: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd52" src="1f552.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock4: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd53" src="1f553.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock5: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd54" src="1f554.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock6: { + keywords: [ "time", "late", "early", "schedule", "dawn", "dusk" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd55" src="1f555.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock7: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd56" src="1f556.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock8: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd57" src="1f557.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock9: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd58" src="1f558.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock10: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd59" src="1f559.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock11: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5a" src="1f55a.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock12: { + keywords: [ "time", "noon", "midnight", "midday", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5b" src="1f55b.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock130: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5c" src="1f55c.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock230: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5d" src="1f55d.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock330: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5e" src="1f55e.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock430: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5f" src="1f55f.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock530: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd60" src="1f560.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock630: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd61" src="1f561.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock730: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd62" src="1f562.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock830: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd63" src="1f563.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock930: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd64" src="1f564.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock1030: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd65" src="1f565.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock1130: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd66" src="1f566.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + clock1230: { + keywords: [ "time", "late", "early", "schedule" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd67" src="1f567.png"/>', + fitzpatrick_scale: false, + category: "symbols" + }, + afghanistan: { + keywords: [ "af", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddeb" src="1f1e6-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + aland_islands: { + keywords: [ "\xc5land", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfd" src="1f1e6-1f1fd.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + albania: { + keywords: [ "al", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf1" src="1f1e6-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + algeria: { + keywords: [ "dz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddff" src="1f1e9-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + american_samoa: { + keywords: [ "american", "ws", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf8" src="1f1e6-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + andorra: { + keywords: [ "ad", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\udde9" src="1f1e6-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + angola: { + keywords: [ "ao", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf4" src="1f1e6-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + anguilla: { + keywords: [ "ai", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddee" src="1f1e6-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + antarctica: { + keywords: [ "aq", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf6" src="1f1e6-1f1f6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + antigua_barbuda: { + keywords: [ "antigua", "barbuda", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddec" src="1f1e6-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + argentina: { + keywords: [ "ar", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf7" src="1f1e6-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + armenia: { + keywords: [ "am", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf2" src="1f1e6-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + aruba: { + keywords: [ "aw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfc" src="1f1e6-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + australia: { + keywords: [ "au", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfa" src="1f1e6-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + austria: { + keywords: [ "at", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf9" src="1f1e6-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + azerbaijan: { + keywords: [ "az", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddff" src="1f1e6-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bahamas: { + keywords: [ "bs", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf8" src="1f1e7-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bahrain: { + keywords: [ "bh", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udded" src="1f1e7-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bangladesh: { + keywords: [ "bd", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde9" src="1f1e7-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + barbados: { + keywords: [ "bb", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde7" src="1f1e7-1f1e7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + belarus: { + keywords: [ "by", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddfe" src="1f1e7-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + belgium: { + keywords: [ "be", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddea" src="1f1e7-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + belize: { + keywords: [ "bz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddff" src="1f1e7-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + benin: { + keywords: [ "bj", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddef" src="1f1e7-1f1ef.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bermuda: { + keywords: [ "bm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf2" src="1f1e7-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bhutan: { + keywords: [ "bt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf9" src="1f1e7-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bolivia: { + keywords: [ "bo", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf4" src="1f1e7-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + caribbean_netherlands: { + keywords: [ "bonaire", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf6" src="1f1e7-1f1f6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bosnia_herzegovina: { + keywords: [ "bosnia", "herzegovina", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde6" src="1f1e7-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + botswana: { + keywords: [ "bw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddfc" src="1f1e7-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + brazil: { + keywords: [ "br", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf7" src="1f1e7-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + british_indian_ocean_territory: { + keywords: [ "british", "indian", "ocean", "territory", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf4" src="1f1ee-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + british_virgin_islands: { + keywords: [ "british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddec" src="1f1fb-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + brunei: { + keywords: [ "bn", "darussalam", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf3" src="1f1e7-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + bulgaria: { + keywords: [ "bg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddec" src="1f1e7-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + burkina_faso: { + keywords: [ "burkina", "faso", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddeb" src="1f1e7-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + burundi: { + keywords: [ "bi", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddee" src="1f1e7-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cape_verde: { + keywords: [ "cabo", "verde", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfb" src="1f1e8-1f1fb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cambodia: { + keywords: [ "kh", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\udded" src="1f1f0-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cameroon: { + keywords: [ "cm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf2" src="1f1e8-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + canada: { + keywords: [ "ca", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde6" src="1f1e8-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + canary_islands: { + keywords: [ "canary", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\udde8" src="1f1ee-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cayman_islands: { + keywords: [ "cayman", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddfe" src="1f1f0-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + central_african_republic: { + keywords: [ "central", "african", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddeb" src="1f1e8-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + chad: { + keywords: [ "td", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udde9" src="1f1f9-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + chile: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf1" src="1f1e8-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cn: { + keywords: [ "china", "chinese", "prc", "flag", "country", "nation", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf3" src="1f1e8-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + christmas_island: { + keywords: [ "christmas", "island", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfd" src="1f1e8-1f1fd.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cocos_islands: { + keywords: [ "cocos", "keeling", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde8" src="1f1e8-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + colombia: { + keywords: [ "co", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf4" src="1f1e8-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + comoros: { + keywords: [ "km", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf2" src="1f1f0-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + congo_brazzaville: { + keywords: [ "congo", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddec" src="1f1e8-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + congo_kinshasa: { + keywords: [ "congo", "democratic", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde9" src="1f1e8-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cook_islands: { + keywords: [ "cook", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf0" src="1f1e8-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + costa_rica: { + keywords: [ "costa", "rica", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf7" src="1f1e8-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + croatia: { + keywords: [ "hr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf7" src="1f1ed-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cuba: { + keywords: [ "cu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfa" src="1f1e8-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + curacao: { + keywords: [ "cura\xe7ao", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfc" src="1f1e8-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cyprus: { + keywords: [ "cy", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfe" src="1f1e8-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + czech_republic: { + keywords: [ "cz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddff" src="1f1e8-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + denmark: { + keywords: [ "dk", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf0" src="1f1e9-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + djibouti: { + keywords: [ "dj", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddef" src="1f1e9-1f1ef.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + dominica: { + keywords: [ "dm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf2" src="1f1e9-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + dominican_republic: { + keywords: [ "dominican", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf4" src="1f1e9-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ecuador: { + keywords: [ "ec", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\udde8" src="1f1ea-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + egypt: { + keywords: [ "eg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddec" src="1f1ea-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + el_salvador: { + keywords: [ "el", "salvador", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfb" src="1f1f8-1f1fb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + equatorial_guinea: { + keywords: [ "equatorial", "gn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf6" src="1f1ec-1f1f6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + eritrea: { + keywords: [ "er", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf7" src="1f1ea-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + estonia: { + keywords: [ "ee", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddea" src="1f1ea-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ethiopia: { + keywords: [ "et", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf9" src="1f1ea-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + eu: { + keywords: [ "european", "union", "flag", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddfa" src="1f1ea-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + falkland_islands: { + keywords: [ "falkland", "islands", "malvinas", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf0" src="1f1eb-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + faroe_islands: { + keywords: [ "faroe", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf4" src="1f1eb-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + fiji: { + keywords: [ "fj", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddef" src="1f1eb-1f1ef.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + finland: { + keywords: [ "fi", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddee" src="1f1eb-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + fr: { + keywords: [ "banner", "flag", "nation", "france", "french", "country" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf7" src="1f1eb-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + french_guiana: { + keywords: [ "french", "guiana", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddeb" src="1f1ec-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + french_polynesia: { + keywords: [ "french", "polynesia", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddeb" src="1f1f5-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + french_southern_territories: { + keywords: [ "french", "southern", "territories", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddeb" src="1f1f9-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + gabon: { + keywords: [ "ga", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde6" src="1f1ec-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + gambia: { + keywords: [ "gm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf2" src="1f1ec-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + georgia: { + keywords: [ "ge", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddea" src="1f1ec-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + de: { + keywords: [ "german", "nation", "flag", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddea" src="1f1e9-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ghana: { + keywords: [ "gh", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udded" src="1f1ec-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + gibraltar: { + keywords: [ "gi", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddee" src="1f1ec-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + greece: { + keywords: [ "gr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf7" src="1f1ec-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + greenland: { + keywords: [ "gl", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf1" src="1f1ec-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + grenada: { + keywords: [ "gd", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde9" src="1f1ec-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guadeloupe: { + keywords: [ "gp", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf5" src="1f1ec-1f1f5.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guam: { + keywords: [ "gu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfa" src="1f1ec-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guatemala: { + keywords: [ "gt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf9" src="1f1ec-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guernsey: { + keywords: [ "gg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddec" src="1f1ec-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guinea: { + keywords: [ "gn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf3" src="1f1ec-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guinea_bissau: { + keywords: [ "gw", "bissau", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfc" src="1f1ec-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + guyana: { + keywords: [ "gy", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfe" src="1f1ec-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + haiti: { + keywords: [ "ht", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf9" src="1f1ed-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + honduras: { + keywords: [ "hn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf3" src="1f1ed-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + hong_kong: { + keywords: [ "hong", "kong", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf0" src="1f1ed-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + hungary: { + keywords: [ "hu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddfa" src="1f1ed-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + iceland: { + keywords: [ "is", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf8" src="1f1ee-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + india: { + keywords: [ "in", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf3" src="1f1ee-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + indonesia: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\udde9" src="1f1ee-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + iran: { + keywords: [ "iran,", "islamic", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf7" src="1f1ee-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + iraq: { + keywords: [ "iq", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf6" src="1f1ee-1f1f6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ireland: { + keywords: [ "ie", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddea" src="1f1ee-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + isle_of_man: { + keywords: [ "isle", "man", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf2" src="1f1ee-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + israel: { + keywords: [ "il", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf1" src="1f1ee-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + it: { + keywords: [ "italy", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf9" src="1f1ee-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + cote_divoire: { + keywords: [ "ivory", "coast", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddee" src="1f1e8-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + jamaica: { + keywords: [ "jm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf2" src="1f1ef-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + jp: { + keywords: [ "japanese", "nation", "flag", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf5" src="1f1ef-1f1f5.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + jersey: { + keywords: [ "je", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddea" src="1f1ef-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + jordan: { + keywords: [ "jo", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf4" src="1f1ef-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kazakhstan: { + keywords: [ "kz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddff" src="1f1f0-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kenya: { + keywords: [ "ke", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddea" src="1f1f0-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kiribati: { + keywords: [ "ki", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddee" src="1f1f0-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kosovo: { + keywords: [ "xk", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfd\ud83c\uddf0" src="1f1fd-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kuwait: { + keywords: [ "kw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddfc" src="1f1f0-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kyrgyzstan: { + keywords: [ "kg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddec" src="1f1f0-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + laos: { + keywords: [ "lao", "democratic", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde6" src="1f1f1-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + latvia: { + keywords: [ "lv", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfb" src="1f1f1-1f1fb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + lebanon: { + keywords: [ "lb", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde7" src="1f1f1-1f1e7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + lesotho: { + keywords: [ "ls", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf8" src="1f1f1-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + liberia: { + keywords: [ "lr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf7" src="1f1f1-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + libya: { + keywords: [ "ly", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfe" src="1f1f1-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + liechtenstein: { + keywords: [ "li", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddee" src="1f1f1-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + lithuania: { + keywords: [ "lt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf9" src="1f1f1-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + luxembourg: { + keywords: [ "lu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfa" src="1f1f1-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + macau: { + keywords: [ "macao", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf4" src="1f1f2-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + macedonia: { + keywords: [ "macedonia,", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf0" src="1f1f2-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + madagascar: { + keywords: [ "mg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddec" src="1f1f2-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + malawi: { + keywords: [ "mw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfc" src="1f1f2-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + malaysia: { + keywords: [ "my", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfe" src="1f1f2-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + maldives: { + keywords: [ "mv", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfb" src="1f1f2-1f1fb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mali: { + keywords: [ "ml", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf1" src="1f1f2-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + malta: { + keywords: [ "mt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf9" src="1f1f2-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + marshall_islands: { + keywords: [ "marshall", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udded" src="1f1f2-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + martinique: { + keywords: [ "mq", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf6" src="1f1f2-1f1f6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mauritania: { + keywords: [ "mr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf7" src="1f1f2-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mauritius: { + keywords: [ "mu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfa" src="1f1f2-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mayotte: { + keywords: [ "yt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfe\ud83c\uddf9" src="1f1fe-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mexico: { + keywords: [ "mx", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfd" src="1f1f2-1f1fd.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + micronesia: { + keywords: [ "micronesia,", "federated", "states", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf2" src="1f1eb-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + moldova: { + keywords: [ "moldova,", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde9" src="1f1f2-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + monaco: { + keywords: [ "mc", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde8" src="1f1f2-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mongolia: { + keywords: [ "mn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf3" src="1f1f2-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + montenegro: { + keywords: [ "me", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddea" src="1f1f2-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + montserrat: { + keywords: [ "ms", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf8" src="1f1f2-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + morocco: { + keywords: [ "ma", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde6" src="1f1f2-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + mozambique: { + keywords: [ "mz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddff" src="1f1f2-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + myanmar: { + keywords: [ "mm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf2" src="1f1f2-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + namibia: { + keywords: [ "na", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\udde6" src="1f1f3-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + nauru: { + keywords: [ "nr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf7" src="1f1f3-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + nepal: { + keywords: [ "np", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf5" src="1f1f3-1f1f5.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + netherlands: { + keywords: [ "nl", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf1" src="1f1f3-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + new_caledonia: { + keywords: [ "new", "caledonia", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\udde8" src="1f1f3-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + new_zealand: { + keywords: [ "new", "zealand", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddff" src="1f1f3-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + nicaragua: { + keywords: [ "ni", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddee" src="1f1f3-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + niger: { + keywords: [ "ne", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddea" src="1f1f3-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + nigeria: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddec" src="1f1f3-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + niue: { + keywords: [ "nu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddfa" src="1f1f3-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + norfolk_island: { + keywords: [ "norfolk", "island", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddeb" src="1f1f3-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + northern_mariana_islands: { + keywords: [ "northern", "mariana", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf5" src="1f1f2-1f1f5.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + north_korea: { + keywords: [ "north", "korea", "nation", "flag", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf5" src="1f1f0-1f1f5.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + norway: { + keywords: [ "no", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf4" src="1f1f3-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + oman: { + keywords: [ "om_symbol", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf4\ud83c\uddf2" src="1f1f4-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + pakistan: { + keywords: [ "pk", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf0" src="1f1f5-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + palau: { + keywords: [ "pw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddfc" src="1f1f5-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + palestinian_territories: { + keywords: [ "palestine", "palestinian", "territories", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf8" src="1f1f5-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + panama: { + keywords: [ "pa", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\udde6" src="1f1f5-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + papua_new_guinea: { + keywords: [ "papua", "new", "guinea", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddec" src="1f1f5-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + paraguay: { + keywords: [ "py", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddfe" src="1f1f5-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + peru: { + keywords: [ "pe", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddea" src="1f1f5-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + philippines: { + keywords: [ "ph", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\udded" src="1f1f5-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + pitcairn_islands: { + keywords: [ "pitcairn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf3" src="1f1f5-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + poland: { + keywords: [ "pl", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf1" src="1f1f5-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + portugal: { + keywords: [ "pt", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf9" src="1f1f5-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + puerto_rico: { + keywords: [ "puerto", "rico", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf7" src="1f1f5-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + qatar: { + keywords: [ "qa", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf6\ud83c\udde6" src="1f1f6-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + reunion: { + keywords: [ "r\xe9union", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddea" src="1f1f7-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + romania: { + keywords: [ "ro", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddf4" src="1f1f7-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ru: { + keywords: [ "russian", "federation", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddfa" src="1f1f7-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + rwanda: { + keywords: [ "rw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddfc" src="1f1f7-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_barthelemy: { + keywords: [ "saint", "barth\xe9lemy", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf1" src="1f1e7-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_helena: { + keywords: [ "saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udded" src="1f1f8-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_kitts_nevis: { + keywords: [ "saint", "kitts", "nevis", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf3" src="1f1f0-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_lucia: { + keywords: [ "saint", "lucia", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde8" src="1f1f1-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_pierre_miquelon: { + keywords: [ "saint", "pierre", "miquelon", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf2" src="1f1f5-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + st_vincent_grenadines: { + keywords: [ "saint", "vincent", "grenadines", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\udde8" src="1f1fb-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + samoa: { + keywords: [ "ws", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfc\ud83c\uddf8" src="1f1fc-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + san_marino: { + keywords: [ "san", "marino", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf2" src="1f1f8-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sao_tome_principe: { + keywords: [ "sao", "tome", "principe", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf9" src="1f1f8-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + saudi_arabia: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde6" src="1f1f8-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + senegal: { + keywords: [ "sn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf3" src="1f1f8-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + serbia: { + keywords: [ "rs", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddf8" src="1f1f7-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + seychelles: { + keywords: [ "sc", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde8" src="1f1f8-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sierra_leone: { + keywords: [ "sierra", "leone", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf1" src="1f1f8-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + singapore: { + keywords: [ "sg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddec" src="1f1f8-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sint_maarten: { + keywords: [ "sint", "maarten", "dutch", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfd" src="1f1f8-1f1fd.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + slovakia: { + keywords: [ "sk", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf0" src="1f1f8-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + slovenia: { + keywords: [ "si", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddee" src="1f1f8-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + solomon_islands: { + keywords: [ "solomon", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde7" src="1f1f8-1f1e7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + somalia: { + keywords: [ "so", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf4" src="1f1f8-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + south_africa: { + keywords: [ "south", "africa", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\udde6" src="1f1ff-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + south_georgia_south_sandwich_islands: { + keywords: [ "south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf8" src="1f1ec-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + kr: { + keywords: [ "south", "korea", "nation", "flag", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf7" src="1f1f0-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + south_sudan: { + keywords: [ "south", "sd", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf8" src="1f1f8-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + es: { + keywords: [ "spain", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf8" src="1f1ea-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sri_lanka: { + keywords: [ "sri", "lanka", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf0" src="1f1f1-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sudan: { + keywords: [ "sd", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde9" src="1f1f8-1f1e9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + suriname: { + keywords: [ "sr", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf7" src="1f1f8-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + swaziland: { + keywords: [ "sz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddff" src="1f1f8-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + sweden: { + keywords: [ "se", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddea" src="1f1f8-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + switzerland: { + keywords: [ "ch", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udded" src="1f1e8-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + syria: { + keywords: [ "syrian", "arab", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfe" src="1f1f8-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + taiwan: { + keywords: [ "tw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddfc" src="1f1f9-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tajikistan: { + keywords: [ "tj", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddef" src="1f1f9-1f1ef.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tanzania: { + keywords: [ "tanzania,", "united", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddff" src="1f1f9-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + thailand: { + keywords: [ "th", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udded" src="1f1f9-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + timor_leste: { + keywords: [ "timor", "leste", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf1" src="1f1f9-1f1f1.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + togo: { + keywords: [ "tg", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddec" src="1f1f9-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tokelau: { + keywords: [ "tk", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf0" src="1f1f9-1f1f0.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tonga: { + keywords: [ "to", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf4" src="1f1f9-1f1f4.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + trinidad_tobago: { + keywords: [ "trinidad", "tobago", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf9" src="1f1f9-1f1f9.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tunisia: { + keywords: [ "tn", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf3" src="1f1f9-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tr: { + keywords: [ "turkey", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf7" src="1f1f9-1f1f7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + turkmenistan: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf2" src="1f1f9-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + turks_caicos_islands: { + keywords: [ "turks", "caicos", "islands", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udde8" src="1f1f9-1f1e8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + tuvalu: { + keywords: [ "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddfb" src="1f1f9-1f1fb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + uganda: { + keywords: [ "ug", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddec" src="1f1fa-1f1ec.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + ukraine: { + keywords: [ "ua", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\udde6" src="1f1fa-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + united_arab_emirates: { + keywords: [ "united", "arab", "emirates", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddea" src="1f1e6-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + uk: { + keywords: [ "united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde7" src="1f1ec-1f1e7.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + england: { + keywords: [ "flag", "english" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f" src="1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + scotland: { + keywords: [ "flag", "scottish" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f" src="1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + wales: { + keywords: [ "flag", "welsh" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f" src="1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + us: { + keywords: [ "united", "states", "america", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddf8" src="1f1fa-1f1f8.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + us_virgin_islands: { + keywords: [ "virgin", "islands", "us", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddee" src="1f1fb-1f1ee.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + uruguay: { + keywords: [ "uy", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddfe" src="1f1fa-1f1fe.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + uzbekistan: { + keywords: [ "uz", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddff" src="1f1fa-1f1ff.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + vanuatu: { + keywords: [ "vu", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddfa" src="1f1fb-1f1fa.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + vatican_city: { + keywords: [ "vatican", "city", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\udde6" src="1f1fb-1f1e6.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + venezuela: { + keywords: [ "ve", "bolivarian", "republic", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddea" src="1f1fb-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + vietnam: { + keywords: [ "viet", "nam", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddf3" src="1f1fb-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + wallis_futuna: { + keywords: [ "wallis", "futuna", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfc\ud83c\uddeb" src="1f1fc-1f1eb.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + western_sahara: { + keywords: [ "western", "sahara", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\udded" src="1f1ea-1f1ed.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + yemen: { + keywords: [ "ye", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfe\ud83c\uddea" src="1f1fe-1f1ea.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + zambia: { + keywords: [ "zm", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\uddf2" src="1f1ff-1f1f2.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + zimbabwe: { + keywords: [ "zw", "flag", "nation", "country", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\uddfc" src="1f1ff-1f1fc.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + united_nations: { + keywords: [ "un", "flag", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddf3" src="1f1fa-1f1f3.png"/>', + fitzpatrick_scale: false, + category: "flags" + }, + pirate_flag: { + keywords: [ "skull", "crossbones", "flag", "banner" ], + char: '<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\u200d\u2620\ufe0f" src="1f3f4-200d-2620-fe0f.png"/>', + fitzpatrick_scale: false, + category: "flags" + } +}); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.min.js b/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.min.js new file mode 100644 index 000000000..a0451629e --- /dev/null +++ b/apps/st/public/tinymce/plugins/emoticons/js/emojiimages.min.js @@ -0,0 +1,3 @@ +// Source: npm package: emojilib +// Images provided by twemoji: https://github.com/twitter/twemoji +window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcaf" src="1f4af.png"/>',fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd22" src="1f522.png"/>',fitzpatrick_scale:!1,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude00" src="1f600.png"/>',fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2c" src="1f62c.png"/>',fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude01" src="1f601.png"/>',fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude02" src="1f602.png"/>',fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd23" src="1f923.png"/>',fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd73" src="1f973.png"/>',fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude03" src="1f603.png"/>',fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude04" src="1f604.png"/>',fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude05" src="1f605.png"/>',fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude06" src="1f606.png"/>',fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude07" src="1f607.png"/>',fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude09" src="1f609.png"/>',fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0a" src="1f60a.png"/>',fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude42" src="1f642.png"/>',fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude43" src="1f643.png"/>',fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u263a\ufe0f" src="263a.png"/>',fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0b" src="1f60b.png"/>',fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0c" src="1f60c.png"/>',fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0d" src="1f60d.png"/>',fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd70" src="1f970.png"/>',fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude18" src="1f618.png"/>',fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude17" src="1f617.png"/>',fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude19" src="1f619.png"/>',fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1a" src="1f61a.png"/>',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1c" src="1f61c.png"/>',fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2a" src="1f92a.png"/>',fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd28" src="1f928.png"/>',fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd0" src="1f9d0.png"/>',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1d" src="1f61d.png"/>',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1b" src="1f61b.png"/>',fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd11" src="1f911.png"/>',fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd13" src="1f913.png"/>',fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0e" src="1f60e.png"/>',fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd29" src="1f929.png"/>',fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd21" src="1f921.png"/>',fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd20" src="1f920.png"/>',fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd17" src="1f917.png"/>',fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude0f" src="1f60f.png"/>',fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude36" src="1f636.png"/>',fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude10" src="1f610.png"/>',fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude11" src="1f611.png"/>',fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude12" src="1f612.png"/>',fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude44" src="1f644.png"/>',fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd14" src="1f914.png"/>',fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd25" src="1f925.png"/>',fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2d" src="1f92d.png"/>',fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2b" src="1f92b.png"/>',fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2c" src="1f92c.png"/>',fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2f" src="1f92f.png"/>',fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude33" src="1f633.png"/>',fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1e" src="1f61e.png"/>',fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude1f" src="1f61f.png"/>',fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude20" src="1f620.png"/>',fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude21" src="1f621.png"/>',fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude14" src="1f614.png"/>',fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude15" src="1f615.png"/>',fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude41" src="1f641.png"/>',fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2639" src="2639.png"/>',fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude23" src="1f623.png"/>',fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude16" src="1f616.png"/>',fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2b" src="1f62b.png"/>',fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude29" src="1f629.png"/>',fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7a" src="1f97a.png"/>',fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude24" src="1f624.png"/>',fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2e" src="1f62e.png"/>',fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude31" src="1f631.png"/>',fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude28" src="1f628.png"/>',fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude30" src="1f630.png"/>',fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2f" src="1f62f.png"/>',fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude26" src="1f626.png"/>',fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude27" src="1f627.png"/>',fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude22" src="1f622.png"/>',fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude25" src="1f625.png"/>',fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd24" src="1f924.png"/>',fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2a" src="1f62a.png"/>',fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude13" src="1f613.png"/>',fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd75" src="1f975.png"/>',fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd76" src="1f976.png"/>',fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude2d" src="1f62d.png"/>',fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude35" src="1f635.png"/>',fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude32" src="1f632.png"/>',fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd10" src="1f910.png"/>',fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd22" src="1f922.png"/>',fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd27" src="1f927.png"/>',fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd2e" src="1f92e.png"/>',fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude37" src="1f637.png"/>',fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd12" src="1f912.png"/>',fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd15" src="1f915.png"/>',fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd74" src="1f974.png"/>',fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude34" src="1f634.png"/>',fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca4" src="1f4a4.png"/>',fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca9" src="1f4a9.png"/>',fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude08" src="1f608.png"/>',fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7f" src="1f47f.png"/>',fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc79" src="1f479.png"/>',fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7a" src="1f47a.png"/>',fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc80" src="1f480.png"/>',fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7b" src="1f47b.png"/>',fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7d" src="1f47d.png"/>',fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd16" src="1f916.png"/>',fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3a" src="1f63a.png"/>',fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude38" src="1f638.png"/>',fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude39" src="1f639.png"/>',fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3b" src="1f63b.png"/>',fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3c" src="1f63c.png"/>',fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3d" src="1f63d.png"/>',fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude40" src="1f640.png"/>',fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3f" src="1f63f.png"/>',fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude3e" src="1f63e.png"/>',fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd32" src="1f932.png"/>',fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4c" src="1f64c.png"/>',fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4f" src="1f44f.png"/>',fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4b" src="1f44b.png"/>',fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd19" src="1f919.png"/>',fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4d" src="1f44d.png"/>',fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4e" src="1f44e.png"/>',fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4a" src="1f44a.png"/>',fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270a" src="270a.png"/>',fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1b" src="1f91b.png"/>',fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1c" src="1f91c.png"/>',fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270c" src="270c.png"/>',fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc4c" src="1f44c.png"/>',fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270b" src="270b.png"/>',fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1a" src="1f91a.png"/>',fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc50" src="1f450.png"/>',fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcaa" src="1f4aa.png"/>',fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4f" src="1f64f.png"/>',fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb6" src="1f9b6.png"/>',fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb5" src="1f9b5.png"/>',fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1d" src="1f91d.png"/>',fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u261d" src="261d.png"/>',fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc46" src="1f446.png"/>',fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc47" src="1f447.png"/>',fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc48" src="1f448.png"/>',fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc49" src="1f449.png"/>',fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd95" src="1f595.png"/>',fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd90" src="1f590.png"/>',fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1f" src="1f91f.png"/>',fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd18" src="1f918.png"/>',fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd1e" src="1f91e.png"/>',fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd96" src="1f596.png"/>',fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270d" src="270d.png"/>',fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd33" src="1f933.png"/>',fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc85" src="1f485.png"/>',fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc44" src="1f444.png"/>',fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb7" src="1f9b7.png"/>',fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc45" src="1f445.png"/>',fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc42" src="1f442.png"/>',fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc43" src="1f443.png"/>',fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc41" src="1f441.png"/>',fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc40" src="1f440.png"/>',fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde0" src="1f9e0.png"/>',fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc64" src="1f464.png"/>',fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc65" src="1f465.png"/>',fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde3" src="1f5e3.png"/>',fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc76" src="1f476.png"/>',fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd2" src="1f9d2.png"/>',fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc66" src="1f466.png"/>',fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc67" src="1f467.png"/>',fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd1" src="1f9d1.png"/>',fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68" src="1f468.png"/>',fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69" src="1f469.png"/>',fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc71\u200d\u2640\ufe0f" src="1f471-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc71" src="1f471.png"/>',fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd4" src="1f9d4.png"/>',fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd3" src="1f9d3.png"/>',fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc74" src="1f474.png"/>',fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc75" src="1f475.png"/>',fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc72" src="1f472.png"/>',fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd5" src="1f9d5.png"/>',fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc73\u200d\u2640\ufe0f" src="1f473-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc73" src="1f473.png"/>',fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6e\u200d\u2640\ufe0f" src="1f46e-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6e" src="1f46e.png"/>',fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc77\u200d\u2640\ufe0f" src="1f477-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc77" src="1f477.png"/>',fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc82\u200d\u2640\ufe0f" src="1f482-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc82" src="1f482.png"/>',fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd75\ufe0f\u200d\u2640\ufe0f" src="1f575-fe0f-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd75" src="1f575.png"/>',fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2695\ufe0f" src="1f469-200d-2695-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2695\ufe0f" src="1f468-200d-2695-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf3e" src="1f469-200d-1f33e.png"/>',fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf3e" src="1f468-200d-1f33e.png"/>',fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf73" src="1f469-200d-1f373.png"/>',fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf73" src="1f468-200d-1f373.png"/>',fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udf93" src="1f469-200d-1f393.png"/>',fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udf93" src="1f468-200d-1f393.png"/>',fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfa4" src="1f469-200d-1f3a4.png"/>',fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfa4" src="1f468-200d-1f3a4.png"/>',fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfeb" src="1f469-200d-1f3eb.png"/>',fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfeb" src="1f468-200d-1f3eb.png"/>',fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfed" src="1f469-200d-1f3ed.png"/>',fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfed" src="1f468-200d-1f3ed.png"/>',fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udcbb" src="1f469-200d-1f4bb.png"/>',fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udcbb" src="1f468-200d-1f4bb.png"/>',fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udcbc" src="1f469-200d-1f4bc.png"/>',fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udcbc" src="1f468-200d-1f4bc.png"/>',fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udd27" src="1f469-200d-1f527.png"/>',fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udd27" src="1f468-200d-1f527.png"/>',fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udd2c" src="1f469-200d-1f52c.png"/>',fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udd2c" src="1f468-200d-1f52c.png"/>',fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83c\udfa8" src="1f469-200d-1f3a8.png"/>',fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83c\udfa8" src="1f468-200d-1f3a8.png"/>',fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\ude92" src="1f469-200d-1f692.png"/>',fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\ude92" src="1f468-200d-1f692.png"/>',fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2708\ufe0f" src="1f469-200d-2708-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2708\ufe0f" src="1f468-200d-2708-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\ude80" src="1f469-200d-1f680.png"/>',fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\ude80" src="1f468-200d-1f680.png"/>',fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2696\ufe0f" src="1f469-200d-2696-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2696\ufe0f" src="1f468-200d-2696-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb8\u200d\u2640\ufe0f" src="1f9b8-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb8\u200d\u2642\ufe0f" src="1f9b8-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb9\u200d\u2640\ufe0f" src="1f9b9-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb9\u200d\u2642\ufe0f" src="1f9b9-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd36" src="1f936.png"/>',fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf85" src="1f385.png"/>',fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd9\u200d\u2640\ufe0f" src="1f9d9-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd9\u200d\u2642\ufe0f" src="1f9d9-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddd\u200d\u2640\ufe0f" src="1f9dd-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddd\u200d\u2642\ufe0f" src="1f9dd-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddb\u200d\u2640\ufe0f" src="1f9db-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddb\u200d\u2642\ufe0f" src="1f9db-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddf\u200d\u2640\ufe0f" src="1f9df-200d-2640-fe0f.png"/>',fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddf\u200d\u2642\ufe0f" src="1f9df-200d-2642-fe0f.png"/>',fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddde\u200d\u2640\ufe0f" src="1f9de-200d-2640-fe0f.png"/>',fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddde\u200d\u2642\ufe0f" src="1f9de-200d-2642-fe0f.png"/>',fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddc\u200d\u2640\ufe0f" src="1f9dc-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udddc\u200d\u2642\ufe0f" src="1f9dc-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddda\u200d\u2640\ufe0f" src="1f9da-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddda\u200d\u2642\ufe0f" src="1f9da-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7c" src="1f47c.png"/>',fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd30" src="1f930.png"/>',fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd31" src="1f931.png"/>',fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc78" src="1f478.png"/>',fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd34" src="1f934.png"/>',fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc70" src="1f470.png"/>',fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd35" src="1f935.png"/>',fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc3\u200d\u2640\ufe0f" src="1f3c3-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc3" src="1f3c3.png"/>',fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb6\u200d\u2640\ufe0f" src="1f6b6-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb6" src="1f6b6.png"/>',fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc83" src="1f483.png"/>',fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd7a" src="1f57a.png"/>',fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6f" src="1f46f.png"/>',fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6f\u200d\u2642\ufe0f" src="1f46f-200d-2642-fe0f.png"/>',fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6b" src="1f46b.png"/>',fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6c" src="1f46c.png"/>',fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6d" src="1f46d.png"/>',fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude47\u200d\u2640\ufe0f" src="1f647-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude47" src="1f647.png"/>',fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd26\u200d\u2642\ufe0f" src="1f926-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd26\u200d\u2640\ufe0f" src="1f926-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd37" src="1f937.png"/>',fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd37\u200d\u2642\ufe0f" src="1f937-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc81" src="1f481.png"/>',fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc81\u200d\u2642\ufe0f" src="1f481-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude45" src="1f645.png"/>',fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude45\u200d\u2642\ufe0f" src="1f645-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude46" src="1f646.png"/>',fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude46\u200d\u2642\ufe0f" src="1f646-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4b" src="1f64b.png"/>',fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4b\u200d\u2642\ufe0f" src="1f64b-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4e" src="1f64e.png"/>',fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4e\u200d\u2642\ufe0f" src="1f64e-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4d" src="1f64d.png"/>',fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4d\u200d\u2642\ufe0f" src="1f64d-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc87" src="1f487.png"/>',fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc87\u200d\u2642\ufe0f" src="1f487-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc86" src="1f486.png"/>',fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc86\u200d\u2642\ufe0f" src="1f486-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd6\u200d\u2640\ufe0f" src="1f9d6-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd6\u200d\u2642\ufe0f" src="1f9d6-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc91" src="1f491.png"/>',fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc69" src="1f469-200d-2764-fe0f-200d-1f469.png"/>',fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68" src="1f468-200d-2764-fe0f-200d-1f468.png"/>',fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8f" src="1f48f.png"/>',fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69" src="1f469-200d-2764-fe0f-200d-1f48b-200d-1f469.png"/>',fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68" src="1f468-200d-2764-fe0f-200d-1f48b-200d-1f468.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc6a" src="1f46a.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67" src="1f468-200d-1f469-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f469-200d-1f467-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f469-200d-1f466-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f469-200d-1f467-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67" src="1f469-200d-1f469-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f467-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f469-200d-1f469-200d-1f466-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f469-200d-1f469-200d-1f467-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67" src="1f468-200d-1f468-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f467-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f468-200d-1f466-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f468-200d-1f467-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc66" src="1f469-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67" src="1f469-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f469-200d-1f467-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f469-200d-1f466-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f469-200d-1f467-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc66" src="1f468-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67" src="1f468-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66" src="1f468-200d-1f467-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66" src="1f468-200d-1f466-200d-1f466.png"/>',fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67" src="1f468-200d-1f467-200d-1f467.png"/>',fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf6" src="1f9f6.png"/>',fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf5" src="1f9f5.png"/>',fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde5" src="1f9e5.png"/>',fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7c" src="1f97c.png"/>',fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5a" src="1f45a.png"/>',fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc55" src="1f455.png"/>',fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc56" src="1f456.png"/>',fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc54" src="1f454.png"/>',fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc57" src="1f457.png"/>',fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc59" src="1f459.png"/>',fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc58" src="1f458.png"/>',fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc84" src="1f484.png"/>',fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8b" src="1f48b.png"/>',fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc63" src="1f463.png"/>',fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7f" src="1f97f.png"/>',fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc60" src="1f460.png"/>',fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc61" src="1f461.png"/>',fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc62" src="1f462.png"/>',fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5e" src="1f45e.png"/>',fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5f" src="1f45f.png"/>',fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7e" src="1f97e.png"/>',fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde6" src="1f9e6.png"/>',fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde4" src="1f9e4.png"/>',fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde3" src="1f9e3.png"/>',fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc52" src="1f452.png"/>',fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa9" src="1f3a9.png"/>',fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde2" src="1f9e2.png"/>',fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d1" src="26d1.png"/>',fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf93" src="1f393.png"/>',fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc51" src="1f451.png"/>',fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf92" src="1f392.png"/>',fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf3" src="1f9f3.png"/>',fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5d" src="1f45d.png"/>',fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5b" src="1f45b.png"/>',fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc5c" src="1f45c.png"/>',fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbc" src="1f4bc.png"/>',fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc53" src="1f453.png"/>',fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd76" src="1f576.png"/>',fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd7d" src="1f97d.png"/>',fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8d" src="1f48d.png"/>',fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf02" src="1f302.png"/>',fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc36" src="1f436.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc31" src="1f431.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2d" src="1f42d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc39" src="1f439.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc30" src="1f430.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8a" src="1f98a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3b" src="1f43b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3c" src="1f43c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc28" src="1f428.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2f" src="1f42f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd81" src="1f981.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2e" src="1f42e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc37" src="1f437.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3d" src="1f43d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc38" src="1f438.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd91" src="1f991.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc19" src="1f419.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd90" src="1f990.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc35" src="1f435.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8d" src="1f98d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude48" src="1f648.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude49" src="1f649.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude4a" src="1f64a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc12" src="1f412.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc14" src="1f414.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc27" src="1f427.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc26" src="1f426.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc24" src="1f424.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc23" src="1f423.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc25" src="1f425.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd86" src="1f986.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd85" src="1f985.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd89" src="1f989.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd87" src="1f987.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3a" src="1f43a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc17" src="1f417.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc34" src="1f434.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd84" src="1f984.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1d" src="1f41d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1b" src="1f41b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8b" src="1f98b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0c" src="1f40c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1e" src="1f41e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1c" src="1f41c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd97" src="1f997.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd77" src="1f577.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd82" src="1f982.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd80" src="1f980.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0d" src="1f40d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8e" src="1f98e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd96" src="1f996.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd95" src="1f995.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc22" src="1f422.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc20" src="1f420.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1f" src="1f41f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc21" src="1f421.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2c" src="1f42c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd88" src="1f988.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc33" src="1f433.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0b" src="1f40b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0a" src="1f40a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc06" src="1f406.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd93" src="1f993.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc05" src="1f405.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc03" src="1f403.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc02" src="1f402.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc04" src="1f404.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8c" src="1f98c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2a" src="1f42a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc2b" src="1f42b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd92" src="1f992.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc18" src="1f418.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd8f" src="1f98f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc10" src="1f410.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0f" src="1f40f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc11" src="1f411.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc0e" src="1f40e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc16" src="1f416.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc00" src="1f400.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc01" src="1f401.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc13" src="1f413.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd83" src="1f983.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4a" src="1f54a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc15" src="1f415.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc29" src="1f429.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc08" src="1f408.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc07" src="1f407.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3f" src="1f43f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd94" src="1f994.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9d" src="1f99d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd99" src="1f999.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9b" src="1f99b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd98" src="1f998.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda1" src="1f9a1.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda2" src="1f9a2.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9a" src="1f99a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9c" src="1f99c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9e" src="1f99e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd9f" src="1f99f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc3e" src="1f43e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc09" src="1f409.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc32" src="1f432.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf35" src="1f335.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf84" src="1f384.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf32" src="1f332.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf33" src="1f333.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf34" src="1f334.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf31" src="1f331.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3f" src="1f33f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2618" src="2618.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf40" src="1f340.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8d" src="1f38d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8b" src="1f38b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf43" src="1f343.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf42" src="1f342.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf41" src="1f341.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3e" src="1f33e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3a" src="1f33a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3b" src="1f33b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf39" src="1f339.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd40" src="1f940.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf37" src="1f337.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3c" src="1f33c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf38" src="1f338.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc90" src="1f490.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf44" src="1f344.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf30" src="1f330.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf83" src="1f383.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc1a" src="1f41a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd78" src="1f578.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0e" src="1f30e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0d" src="1f30d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0f" src="1f30f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf15" src="1f315.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf16" src="1f316.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf17" src="1f317.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf18" src="1f318.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf11" src="1f311.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf12" src="1f312.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf13" src="1f313.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf14" src="1f314.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1a" src="1f31a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1d" src="1f31d.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1b" src="1f31b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1c" src="1f31c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1e" src="1f31e.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf19" src="1f319.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b50" src="2b50.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf1f" src="1f31f.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcab" src="1f4ab.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2728" src="2728.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2604" src="2604.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2600\ufe0f" src="2600.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf24" src="1f324.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c5" src="26c5.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf25" src="1f325.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf26" src="1f326.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2601\ufe0f" src="2601.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf27" src="1f327.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c8" src="26c8.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf29" src="1f329.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26a1" src="26a1.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd25" src="1f525.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca5" src="1f4a5.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2744\ufe0f" src="2744.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf28" src="1f328.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26c4" src="26c4.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2603" src="2603.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2c" src="1f32c.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca8" src="1f4a8.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2a" src="1f32a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2b" src="1f32b.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2602" src="2602.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2614" src="2614.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca7" src="1f4a7.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca6" src="1f4a6.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0a" src="1f30a.png"/>',fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4f" src="1f34f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4e" src="1f34e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf50" src="1f350.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4a" src="1f34a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4b" src="1f34b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4c" src="1f34c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf49" src="1f349.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf47" src="1f347.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf53" src="1f353.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf48" src="1f348.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf52" src="1f352.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf51" src="1f351.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf4d" src="1f34d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd65" src="1f965.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5d" src="1f95d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6d" src="1f96d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd51" src="1f951.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd66" src="1f966.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf45" src="1f345.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf46" src="1f346.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd52" src="1f952.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd55" src="1f955.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf36" src="1f336.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd54" src="1f954.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf3d" src="1f33d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6c" src="1f96c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf60" src="1f360.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5c" src="1f95c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6f" src="1f36f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd50" src="1f950.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5e" src="1f35e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd56" src="1f956.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6f" src="1f96f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd68" src="1f968.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc0" src="1f9c0.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5a" src="1f95a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd53" src="1f953.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd69" src="1f969.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5e" src="1f95e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf57" src="1f357.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf56" src="1f356.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddb4" src="1f9b4.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf64" src="1f364.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf73" src="1f373.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf54" src="1f354.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5f" src="1f35f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd59" src="1f959.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2d" src="1f32d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf55" src="1f355.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6a" src="1f96a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6b" src="1f96b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5d" src="1f35d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2e" src="1f32e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf2f" src="1f32f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd57" src="1f957.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd58" src="1f958.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5c" src="1f35c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf72" src="1f372.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf65" src="1f365.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd60" src="1f960.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf63" src="1f363.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf71" src="1f371.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5b" src="1f35b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf59" src="1f359.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf5a" src="1f35a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf58" src="1f358.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf62" src="1f362.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf61" src="1f361.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf67" src="1f367.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf68" src="1f368.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf66" src="1f366.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd67" src="1f967.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf70" src="1f370.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc1" src="1f9c1.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd6e" src="1f96e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf82" src="1f382.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6e" src="1f36e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6c" src="1f36c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6d" src="1f36d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6b" src="1f36b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7f" src="1f37f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5f" src="1f95f.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf69" src="1f369.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf6a" src="1f36a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd5b" src="1f95b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7a" src="1f37a.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7b" src="1f37b.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd42" src="1f942.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf77" src="1f377.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd43" src="1f943.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf78" src="1f378.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf79" src="1f379.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7e" src="1f37e.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf76" src="1f376.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf75" src="1f375.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd64" src="1f964.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2615" src="2615.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7c" src="1f37c.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddc2" src="1f9c2.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd44" src="1f944.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf74" src="1f374.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf7d" src="1f37d.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd63" src="1f963.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd61" src="1f961.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd62" src="1f962.png"/>',fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26bd" src="26bd.png"/>',fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc0" src="1f3c0.png"/>',fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc8" src="1f3c8.png"/>',fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26be" src="26be.png"/>',fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4e" src="1f94e.png"/>',fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbe" src="1f3be.png"/>',fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd0" src="1f3d0.png"/>',fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc9" src="1f3c9.png"/>',fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4f" src="1f94f.png"/>',fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb1" src="1f3b1.png"/>',fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f3" src="26f3.png"/>',fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcc\ufe0f\u200d\u2640\ufe0f" src="1f3cc-fe0f-200d-2640-fe0f.png"/>',fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcc" src="1f3cc.png"/>',fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd3" src="1f3d3.png"/>',fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff8" src="1f3f8.png"/>',fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd45" src="1f945.png"/>',fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd2" src="1f3d2.png"/>',fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd1" src="1f3d1.png"/>',fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4d" src="1f94d.png"/>',fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcf" src="1f3cf.png"/>',fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbf" src="1f3bf.png"/>',fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f7" src="26f7.png"/>',fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc2" src="1f3c2.png"/>',fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3a" src="1f93a.png"/>',fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3c\u200d\u2640\ufe0f" src="1f93c-200d-2640-fe0f.png"/>',fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3c\u200d\u2642\ufe0f" src="1f93c-200d-2642-fe0f.png"/>',fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd38\u200d\u2640\ufe0f" src="1f938-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd38\u200d\u2642\ufe0f" src="1f938-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3e\u200d\u2640\ufe0f" src="1f93e-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3e\u200d\u2642\ufe0f" src="1f93e-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f8" src="26f8.png"/>',fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4c" src="1f94c.png"/>',fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef9" src="1f6f9.png"/>',fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef7" src="1f6f7.png"/>',fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff9" src="1f3f9.png"/>',fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa3" src="1f3a3.png"/>',fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4a" src="1f94a.png"/>',fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd4b" src="1f94b.png"/>',fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea3\u200d\u2640\ufe0f" src="1f6a3-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea3" src="1f6a3.png"/>',fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd7\u200d\u2640\ufe0f" src="1f9d7-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd7\u200d\u2642\ufe0f" src="1f9d7-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfca\u200d\u2640\ufe0f" src="1f3ca-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfca" src="1f3ca.png"/>',fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3d\u200d\u2640\ufe0f" src="1f93d-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd3d\u200d\u2642\ufe0f" src="1f93d-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd8\u200d\u2640\ufe0f" src="1f9d8-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddd8\u200d\u2642\ufe0f" src="1f9d8-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc4\u200d\u2640\ufe0f" src="1f3c4-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc4" src="1f3c4.png"/>',fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec0" src="1f6c0.png"/>',fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f9\ufe0f\u200d\u2640\ufe0f" src="26f9-fe0f-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f9" src="26f9.png"/>',fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcb\ufe0f\u200d\u2640\ufe0f" src="1f3cb-fe0f-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcb" src="1f3cb.png"/>',fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb4\u200d\u2640\ufe0f" src="1f6b4-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb4" src="1f6b4.png"/>',fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb5\u200d\u2640\ufe0f" src="1f6b5-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb5" src="1f6b5.png"/>',fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc7" src="1f3c7.png"/>',fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd74" src="1f574.png"/>',fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc6" src="1f3c6.png"/>',fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbd" src="1f3bd.png"/>',fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc5" src="1f3c5.png"/>',fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf96" src="1f396.png"/>',fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd47" src="1f947.png"/>',fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd48" src="1f948.png"/>',fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd49" src="1f949.png"/>',fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf97" src="1f397.png"/>',fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff5" src="1f3f5.png"/>',fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfab" src="1f3ab.png"/>',fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9f" src="1f39f.png"/>',fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfad" src="1f3ad.png"/>',fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa8" src="1f3a8.png"/>',fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfaa" src="1f3aa.png"/>',fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd39\u200d\u2640\ufe0f" src="1f939-200d-2640-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd39\u200d\u2642\ufe0f" src="1f939-200d-2642-fe0f.png"/>',fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa4" src="1f3a4.png"/>',fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa7" src="1f3a7.png"/>',fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbc" src="1f3bc.png"/>',fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb9" src="1f3b9.png"/>',fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udd41" src="1f941.png"/>',fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb7" src="1f3b7.png"/>',fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfba" src="1f3ba.png"/>',fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb8" src="1f3b8.png"/>',fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfbb" src="1f3bb.png"/>',fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfac" src="1f3ac.png"/>',fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfae" src="1f3ae.png"/>',fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc7e" src="1f47e.png"/>',fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfaf" src="1f3af.png"/>',fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb2" src="1f3b2.png"/>',fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb0" src="1f3b0.png"/>',fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde9" src="1f9e9.png"/>',fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb3" src="1f3b3.png"/>',fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude97" src="1f697.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude95" src="1f695.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude99" src="1f699.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8c" src="1f68c.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8e" src="1f68e.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfce" src="1f3ce.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude93" src="1f693.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude91" src="1f691.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude92" src="1f692.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude90" src="1f690.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9a" src="1f69a.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9b" src="1f69b.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9c" src="1f69c.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef4" src="1f6f4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfcd" src="1f3cd.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb2" src="1f6b2.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef5" src="1f6f5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea8" src="1f6a8.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude94" src="1f694.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8d" src="1f68d.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude98" src="1f698.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude96" src="1f696.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea1" src="1f6a1.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea0" src="1f6a0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9f" src="1f69f.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude83" src="1f683.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8b" src="1f68b.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9d" src="1f69d.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude84" src="1f684.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude85" src="1f685.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude88" src="1f688.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude9e" src="1f69e.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude82" src="1f682.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude86" src="1f686.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude87" src="1f687.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8a" src="1f68a.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude89" src="1f689.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef8" src="1f6f8.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude81" src="1f681.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee9" src="1f6e9.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2708\ufe0f" src="2708.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeeb" src="1f6eb.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeec" src="1f6ec.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f5" src="26f5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee5" src="1f6e5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea4" src="1f6a4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f4" src="26f4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef3" src="1f6f3.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude80" src="1f680.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef0" src="1f6f0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcba" src="1f4ba.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udef6" src="1f6f6.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2693" src="2693.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea7" src="1f6a7.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26fd" src="26fd.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\ude8f" src="1f68f.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea6" src="1f6a6.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea5" src="1f6a5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfc1" src="1f3c1.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea2" src="1f6a2.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa1" src="1f3a1.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa2" src="1f3a2.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa0" src="1f3a0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd7" src="1f3d7.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf01" src="1f301.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfc" src="1f5fc.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfed" src="1f3ed.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f2" src="26f2.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf91" src="1f391.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f0" src="26f0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd4" src="1f3d4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfb" src="1f5fb.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0b" src="1f30b.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfe" src="1f5fe.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd5" src="1f3d5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26fa" src="26fa.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfde" src="1f3de.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee3" src="1f6e3.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee4" src="1f6e4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf05" src="1f305.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf04" src="1f304.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdc" src="1f3dc.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd6" src="1f3d6.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdd" src="1f3dd.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf07" src="1f307.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf06" src="1f306.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd9" src="1f3d9.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf03" src="1f303.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf09" src="1f309.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf0c" src="1f30c.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf20" src="1f320.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf87" src="1f387.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf86" src="1f386.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf08" src="1f308.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfd8" src="1f3d8.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff0" src="1f3f0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfef" src="1f3ef.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdf" src="1f3df.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfd" src="1f5fd.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe0" src="1f3e0.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe1" src="1f3e1.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfda" src="1f3da.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe2" src="1f3e2.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfec" src="1f3ec.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe3" src="1f3e3.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe4" src="1f3e4.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe5" src="1f3e5.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe6" src="1f3e6.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe8" src="1f3e8.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfea" src="1f3ea.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfeb" src="1f3eb.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe9" src="1f3e9.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc92" src="1f492.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfdb" src="1f3db.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ea" src="26ea.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4c" src="1f54c.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4d" src="1f54d.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4b" src="1f54b.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26e9" src="26e9.png"/>',fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u231a" src="231a.png"/>',fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf1" src="1f4f1.png"/>',fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf2" src="1f4f2.png"/>',fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbb" src="1f4bb.png"/>',fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2328" src="2328.png"/>',fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda5" src="1f5a5.png"/>',fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda8" src="1f5a8.png"/>',fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddb1" src="1f5b1.png"/>',fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddb2" src="1f5b2.png"/>',fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd79" src="1f579.png"/>',fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udddc" src="1f5dc.png"/>',fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbd" src="1f4bd.png"/>',fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbe" src="1f4be.png"/>',fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcbf" src="1f4bf.png"/>',fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc0" src="1f4c0.png"/>',fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfc" src="1f4fc.png"/>',fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf7" src="1f4f7.png"/>',fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf8" src="1f4f8.png"/>',fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf9" src="1f4f9.png"/>',fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa5" src="1f3a5.png"/>',fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfd" src="1f4fd.png"/>',fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9e" src="1f39e.png"/>',fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcde" src="1f4de.png"/>',fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u260e\ufe0f" src="260e.png"/>',fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdf" src="1f4df.png"/>',fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce0" src="1f4e0.png"/>',fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfa" src="1f4fa.png"/>',fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcfb" src="1f4fb.png"/>',fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf99" src="1f399.png"/>',fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9a" src="1f39a.png"/>',fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf9b" src="1f39b.png"/>',fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udded" src="1f9ed.png"/>',fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f1" src="23f1.png"/>',fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f2" src="23f2.png"/>',fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f0" src="23f0.png"/>',fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd70" src="1f570.png"/>',fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f3" src="23f3.png"/>',fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u231b" src="231b.png"/>',fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce1" src="1f4e1.png"/>',fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0b" src="1f50b.png"/>',fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0c" src="1f50c.png"/>',fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca1" src="1f4a1.png"/>',fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd26" src="1f526.png"/>',fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd6f" src="1f56f.png"/>',fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddef" src="1f9ef.png"/>',fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd1" src="1f5d1.png"/>',fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee2" src="1f6e2.png"/>',fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb8" src="1f4b8.png"/>',fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb5" src="1f4b5.png"/>',fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb4" src="1f4b4.png"/>',fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb6" src="1f4b6.png"/>',fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb7" src="1f4b7.png"/>',fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb0" src="1f4b0.png"/>',fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb3" src="1f4b3.png"/>',fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8e" src="1f48e.png"/>',fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2696" src="2696.png"/>',fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf0" src="1f9f0.png"/>',fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd27" src="1f527.png"/>',fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd28" src="1f528.png"/>',fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2692" src="2692.png"/>',fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee0" src="1f6e0.png"/>',fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26cf" src="26cf.png"/>',fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd29" src="1f529.png"/>',fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2699" src="2699.png"/>',fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf1" src="1f9f1.png"/>',fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d3" src="26d3.png"/>',fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf2" src="1f9f2.png"/>',fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2b" src="1f52b.png"/>',fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca3" src="1f4a3.png"/>',fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde8" src="1f9e8.png"/>',fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2a" src="1f52a.png"/>',fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde1" src="1f5e1.png"/>',fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2694" src="2694.png"/>',fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udee1" src="1f6e1.png"/>',fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeac" src="1f6ac.png"/>',fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2620" src="2620.png"/>',fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26b0" src="26b0.png"/>',fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26b1" src="26b1.png"/>',fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udffa" src="1f3fa.png"/>',fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2e" src="1f52e.png"/>',fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcff" src="1f4ff.png"/>',fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddff" src="1f9ff.png"/>',fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc88" src="1f488.png"/>',fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2697" src="2697.png"/>',fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2d" src="1f52d.png"/>',fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2c" src="1f52c.png"/>',fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd73" src="1f573.png"/>',fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8a" src="1f48a.png"/>',fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc89" src="1f489.png"/>',fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddec" src="1f9ec.png"/>',fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udda0" src="1f9a0.png"/>',fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddeb" src="1f9eb.png"/>',fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddea" src="1f9ea.png"/>',fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf21" src="1f321.png"/>',fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf9" src="1f9f9.png"/>',fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfa" src="1f9fa.png"/>',fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfb" src="1f9fb.png"/>',fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff7" src="1f3f7.png"/>',fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd16" src="1f516.png"/>',fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebd" src="1f6bd.png"/>',fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebf" src="1f6bf.png"/>',fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec1" src="1f6c1.png"/>',fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfc" src="1f9fc.png"/>',fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfd" src="1f9fd.png"/>',fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf4" src="1f9f4.png"/>',fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd11" src="1f511.png"/>',fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udddd" src="1f5dd.png"/>',fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecb" src="1f6cb.png"/>',fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecc" src="1f6cc.png"/>',fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecf" src="1f6cf.png"/>',fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeaa" src="1f6aa.png"/>',fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udece" src="1f6ce.png"/>',fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf8" src="1f9f8.png"/>',fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddbc" src="1f5bc.png"/>',fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddfa" src="1f5fa.png"/>',fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26f1" src="26f1.png"/>',fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddff" src="1f5ff.png"/>',fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udecd" src="1f6cd.png"/>',fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded2" src="1f6d2.png"/>',fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf88" src="1f388.png"/>',fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8f" src="1f38f.png"/>',fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf80" src="1f380.png"/>',fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf81" src="1f381.png"/>',fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8a" src="1f38a.png"/>',fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf89" src="1f389.png"/>',fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8e" src="1f38e.png"/>',fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf90" src="1f390.png"/>',fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf8c" src="1f38c.png"/>',fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfee" src="1f3ee.png"/>',fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde7" src="1f9e7.png"/>',fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2709\ufe0f" src="2709.png"/>',fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce9" src="1f4e9.png"/>',fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce8" src="1f4e8.png"/>',fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce7" src="1f4e7.png"/>',fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc8c" src="1f48c.png"/>',fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcee" src="1f4ee.png"/>',fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcea" src="1f4ea.png"/>',fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udceb" src="1f4eb.png"/>',fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcec" src="1f4ec.png"/>',fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udced" src="1f4ed.png"/>',fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce6" src="1f4e6.png"/>',fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcef" src="1f4ef.png"/>',fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce5" src="1f4e5.png"/>',fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce4" src="1f4e4.png"/>',fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdc" src="1f4dc.png"/>',fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc3" src="1f4c3.png"/>',fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd1" src="1f4d1.png"/>',fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddfe" src="1f9fe.png"/>',fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcca" src="1f4ca.png"/>',fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc8" src="1f4c8.png"/>',fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc9" src="1f4c9.png"/>',fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc4" src="1f4c4.png"/>',fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc5" src="1f4c5.png"/>',fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc6" src="1f4c6.png"/>',fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd3" src="1f5d3.png"/>',fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc7" src="1f4c7.png"/>',fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc3" src="1f5c3.png"/>',fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddf3" src="1f5f3.png"/>',fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc4" src="1f5c4.png"/>',fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccb" src="1f4cb.png"/>',fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddd2" src="1f5d2.png"/>',fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc1" src="1f4c1.png"/>',fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcc2" src="1f4c2.png"/>',fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddc2" src="1f5c2.png"/>',fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddde" src="1f5de.png"/>',fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf0" src="1f4f0.png"/>',fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd3" src="1f4d3.png"/>',fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd5" src="1f4d5.png"/>',fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd7" src="1f4d7.png"/>',fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd8" src="1f4d8.png"/>',fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd9" src="1f4d9.png"/>',fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd4" src="1f4d4.png"/>',fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd2" src="1f4d2.png"/>',fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcda" src="1f4da.png"/>',fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd6" src="1f4d6.png"/>',fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddf7" src="1f9f7.png"/>',fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd17" src="1f517.png"/>',fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcce" src="1f4ce.png"/>',fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd87" src="1f587.png"/>',fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2702\ufe0f" src="2702.png"/>',fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcd0" src="1f4d0.png"/>',fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccf" src="1f4cf.png"/>',fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\uddee" src="1f9ee.png"/>',fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccc" src="1f4cc.png"/>',fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udccd" src="1f4cd.png"/>',fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udea9" src="1f6a9.png"/>',fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff3" src="1f3f3.png"/>',fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4" src="1f3f4.png"/>',fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff3\ufe0f\u200d\ud83c\udf08" src="1f3f3-fe0f-200d-1f308.png"/>',fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd10" src="1f510.png"/>',fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd12" src="1f512.png"/>',fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd13" src="1f513.png"/>',fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0f" src="1f50f.png"/>',fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8a" src="1f58a.png"/>',fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8b" src="1f58b.png"/>',fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2712\ufe0f" src="2712.png"/>',fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdd" src="1f4dd.png"/>',fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u270f\ufe0f" src="270f.png"/>',fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8d" src="1f58d.png"/>',fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd8c" src="1f58c.png"/>',fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0d" src="1f50d.png"/>',fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0e" src="1f50e.png"/>',fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2764\ufe0f" src="2764.png"/>',fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83e\udde1" src="1f9e1.png"/>',fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9b" src="1f49b.png"/>',fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9a" src="1f49a.png"/>',fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc99" src="1f499.png"/>',fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9c" src="1f49c.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udda4" src="1f5a4.png"/>',fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc94" src="1f494.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2763" src="2763.png"/>',fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc95" src="1f495.png"/>',fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9e" src="1f49e.png"/>',fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc93" src="1f493.png"/>',fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc97" src="1f497.png"/>',fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc96" src="1f496.png"/>',fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc98" src="1f498.png"/>',fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9d" src="1f49d.png"/>',fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udc9f" src="1f49f.png"/>',fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262e" src="262e.png"/>',fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u271d" src="271d.png"/>',fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262a" src="262a.png"/>',fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd49" src="1f549.png"/>',fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2638" src="2638.png"/>',fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2721" src="2721.png"/>',fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd2f" src="1f52f.png"/>',fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd4e" src="1f54e.png"/>',fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u262f" src="262f.png"/>',fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2626" src="2626.png"/>',fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded0" src="1f6d0.png"/>',fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ce" src="26ce.png"/>',fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2648" src="2648.png"/>',fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2649" src="2649.png"/>',fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264a" src="264a.png"/>',fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264b" src="264b.png"/>',fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264c" src="264c.png"/>',fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264d" src="264d.png"/>',fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264e" src="264e.png"/>',fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u264f" src="264f.png"/>',fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2650" src="2650.png"/>',fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2651" src="2651.png"/>',fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2652" src="2652.png"/>',fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2653" src="2653.png"/>',fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd94" src="1f194.png"/>',fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u269b" src="269b.png"/>',fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude33" src="1f233.png"/>',fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude39" src="1f239.png"/>',fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2622" src="2622.png"/>',fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2623" src="2623.png"/>',fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf4" src="1f4f4.png"/>',fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf3" src="1f4f3.png"/>',fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude36" src="1f236.png"/>',fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude1a" src="1f21a.png"/>',fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude38" src="1f238.png"/>',fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude3a" src="1f23a.png"/>',fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude37\ufe0f" src="1f237.png"/>',fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2734\ufe0f" src="2734.png"/>',fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd9a" src="1f19a.png"/>',fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude51" src="1f251.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcae" src="1f4ae.png"/>',fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude50" src="1f250.png"/>',fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3299\ufe0f" src="3299.png"/>',fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3297\ufe0f" src="3297.png"/>',fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude34" src="1f234.png"/>',fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude35" src="1f235.png"/>',fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude32" src="1f232.png"/>',fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd70\ufe0f" src="1f170.png"/>',fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd71\ufe0f" src="1f171.png"/>',fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd8e" src="1f18e.png"/>',fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd91" src="1f191.png"/>',fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd7e\ufe0f" src="1f17e.png"/>',fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd98" src="1f198.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26d4" src="26d4.png"/>',fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcdb" src="1f4db.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeab" src="1f6ab.png"/>',fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u274c" src="274c.png"/>',fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b55" src="2b55.png"/>',fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uded1" src="1f6d1.png"/>',fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca2" src="1f4a2.png"/>',fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2668\ufe0f" src="2668.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb7" src="1f6b7.png"/>',fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeaf" src="1f6af.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb3" src="1f6b3.png"/>',fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb1" src="1f6b1.png"/>',fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1e" src="1f51e.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf5" src="1f4f5.png"/>',fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2757" src="2757.png"/>',fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2755" src="2755.png"/>',fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2753" src="2753.png"/>',fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2754" src="2754.png"/>',fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u203c\ufe0f" src="203c.png"/>',fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2049\ufe0f" src="2049.png"/>',fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd05" src="1f505.png"/>',fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd06" src="1f506.png"/>',fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd31" src="1f531.png"/>',fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u269c" src="269c.png"/>',fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u303d\ufe0f" src="303d.png"/>',fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26a0\ufe0f" src="26a0.png"/>',fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb8" src="1f6b8.png"/>',fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd30" src="1f530.png"/>',fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267b\ufe0f" src="267b.png"/>',fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude2f" src="1f22f.png"/>',fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb9" src="1f4b9.png"/>',fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2747\ufe0f" src="2747.png"/>',fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2733\ufe0f" src="2733.png"/>',fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u274e" src="274e.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2705" src="2705.png"/>',fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udca0" src="1f4a0.png"/>',fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf00" src="1f300.png"/>',fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27bf" src="27bf.png"/>',fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udf10" src="1f310.png"/>',fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u24c2\ufe0f" src="24c2.png"/>',fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfe7" src="1f3e7.png"/>',fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude02\ufe0f" src="1f202.png"/>',fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec2" src="1f6c2.png"/>',fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec3" src="1f6c3.png"/>',fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec4" src="1f6c4.png"/>',fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udec5" src="1f6c5.png"/>',fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267f" src="267f.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udead" src="1f6ad.png"/>',fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebe" src="1f6be.png"/>',fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd7f\ufe0f" src="1f17f.png"/>',fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb0" src="1f6b0.png"/>',fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeb9" src="1f6b9.png"/>',fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeba" src="1f6ba.png"/>',fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebc" src="1f6bc.png"/>',fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udebb" src="1f6bb.png"/>',fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udeae" src="1f6ae.png"/>',fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfa6" src="1f3a6.png"/>',fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcf6" src="1f4f6.png"/>',fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\ude01" src="1f201.png"/>',fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd96" src="1f196.png"/>',fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd97" src="1f197.png"/>',fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd99" src="1f199.png"/>',fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd92" src="1f192.png"/>',fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd95" src="1f195.png"/>',fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udd93" src="1f193.png"/>',fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="0\ufe0f\u20e3" src="30-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="1\ufe0f\u20e3" src="31-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="2\ufe0f\u20e3" src="32-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="3\ufe0f\u20e3" src="33-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="4\ufe0f\u20e3" src="34-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="5\ufe0f\u20e3" src="35-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="6\ufe0f\u20e3" src="36-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="7\ufe0f\u20e3" src="37-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="8\ufe0f\u20e3" src="38-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="9\ufe0f\u20e3" src="39-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1f" src="1f51f.png"/>',fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="*\u20e3" src="2a-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23cf\ufe0f" src="23cf.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25b6\ufe0f" src="25b6.png"/>',fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f8" src="23f8.png"/>',fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ed" src="23ed.png"/>',fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23f9" src="23f9.png"/>',fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23fa" src="23fa.png"/>',fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ef" src="23ef.png"/>',fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ee" src="23ee.png"/>',fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23e9" src="23e9.png"/>',fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ea" src="23ea.png"/>',fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd00" src="1f500.png"/>',fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd01" src="1f501.png"/>',fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd02" src="1f502.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25c0\ufe0f" src="25c0.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3c" src="1f53c.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3d" src="1f53d.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23eb" src="23eb.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u23ec" src="23ec.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27a1\ufe0f" src="27a1.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b05\ufe0f" src="2b05.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b06\ufe0f" src="2b06.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b07\ufe0f" src="2b07.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2197\ufe0f" src="2197.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2198\ufe0f" src="2198.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2199\ufe0f" src="2199.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2196\ufe0f" src="2196.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2195\ufe0f" src="2195.png"/>',fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2194\ufe0f" src="2194.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd04" src="1f504.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u21aa\ufe0f" src="21aa.png"/>',fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u21a9\ufe0f" src="21a9.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2934\ufe0f" src="2934.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2935\ufe0f" src="2935.png"/>',fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="#\ufe0f\u20e3" src="23-20e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2139\ufe0f" src="2139.png"/>',fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd24" src="1f524.png"/>',fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd21" src="1f521.png"/>',fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd20" src="1f520.png"/>',fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd23" src="1f523.png"/>',fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb5" src="1f3b5.png"/>',fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb6" src="1f3b6.png"/>',fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u3030\ufe0f" src="3030.png"/>',fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u27b0" src="27b0.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2714\ufe0f" src="2714.png"/>',fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd03" src="1f503.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2795" src="2795.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2796" src="2796.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2797" src="2797.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2716\ufe0f" src="2716.png"/>',fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u267e" src="267e.png"/>',fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb2" src="1f4b2.png"/>',fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcb1" src="1f4b1.png"/>',fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\xa9\ufe0f" src="a9.png"/>',fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\xae\ufe0f" src="ae.png"/>',fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2122\ufe0f" src="2122.png"/>',fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1a" src="1f51a.png"/>',fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd19" src="1f519.png"/>',fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1b" src="1f51b.png"/>',fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1d" src="1f51d.png"/>',fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd1c" src="1f51c.png"/>',fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2611\ufe0f" src="2611.png"/>',fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd18" src="1f518.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26aa" src="26aa.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u26ab" src="26ab.png"/>',fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd34" src="1f534.png"/>',fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd35" src="1f535.png"/>',fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd38" src="1f538.png"/>',fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd39" src="1f539.png"/>',fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd36" src="1f536.png"/>',fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd37" src="1f537.png"/>',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3a" src="1f53a.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25aa\ufe0f" src="25aa.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25ab\ufe0f" src="25ab.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b1b" src="2b1b.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2b1c" src="2b1c.png"/>',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd3b" src="1f53b.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fc\ufe0f" src="25fc.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fb\ufe0f" src="25fb.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fe" src="25fe.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u25fd" src="25fd.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd32" src="1f532.png"/>',fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd33" src="1f533.png"/>',fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd08" src="1f508.png"/>',fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd09" src="1f509.png"/>',fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd0a" src="1f50a.png"/>',fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd07" src="1f507.png"/>',fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce3" src="1f4e3.png"/>',fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udce2" src="1f4e2.png"/>',fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd14" src="1f514.png"/>',fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd15" src="1f515.png"/>',fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udccf" src="1f0cf.png"/>',fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udc04" src="1f004.png"/>',fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2660\ufe0f" src="2660.png"/>',fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2663\ufe0f" src="2663.png"/>',fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2665\ufe0f" src="2665.png"/>',fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\u2666\ufe0f" src="2666.png"/>',fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udfb4" src="1f3b4.png"/>',fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcad" src="1f4ad.png"/>',fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\uddef" src="1f5ef.png"/>',fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udcac" src="1f4ac.png"/>',fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udde8" src="1f5e8.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd50" src="1f550.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd51" src="1f551.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd52" src="1f552.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd53" src="1f553.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd54" src="1f554.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd55" src="1f555.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd56" src="1f556.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd57" src="1f557.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd58" src="1f558.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd59" src="1f559.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5a" src="1f55a.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5b" src="1f55b.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5c" src="1f55c.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5d" src="1f55d.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5e" src="1f55e.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd5f" src="1f55f.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd60" src="1f560.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd61" src="1f561.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd62" src="1f562.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd63" src="1f563.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd64" src="1f564.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd65" src="1f565.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd66" src="1f566.png"/>',fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83d\udd67" src="1f567.png"/>',fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddeb" src="1f1e6-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfd" src="1f1e6-1f1fd.png"/>',fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf1" src="1f1e6-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddff" src="1f1e9-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf8" src="1f1e6-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\udde9" src="1f1e6-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf4" src="1f1e6-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddee" src="1f1e6-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf6" src="1f1e6-1f1f6.png"/>',fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddec" src="1f1e6-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf7" src="1f1e6-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf2" src="1f1e6-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfc" src="1f1e6-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddfa" src="1f1e6-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddf9" src="1f1e6-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddff" src="1f1e6-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf8" src="1f1e7-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udded" src="1f1e7-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde9" src="1f1e7-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde7" src="1f1e7-1f1e7.png"/>',fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddfe" src="1f1e7-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddea" src="1f1e7-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddff" src="1f1e7-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddef" src="1f1e7-1f1ef.png"/>',fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf2" src="1f1e7-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf9" src="1f1e7-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf4" src="1f1e7-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf6" src="1f1e7-1f1f6.png"/>',fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\udde6" src="1f1e7-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddfc" src="1f1e7-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf7" src="1f1e7-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf4" src="1f1ee-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddec" src="1f1fb-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf3" src="1f1e7-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddec" src="1f1e7-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddeb" src="1f1e7-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddee" src="1f1e7-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfb" src="1f1e8-1f1fb.png"/>',fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\udded" src="1f1f0-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf2" src="1f1e8-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde6" src="1f1e8-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\udde8" src="1f1ee-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddfe" src="1f1f0-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddeb" src="1f1e8-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udde9" src="1f1f9-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf1" src="1f1e8-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf3" src="1f1e8-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfd" src="1f1e8-1f1fd.png"/>',fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde8" src="1f1e8-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf4" src="1f1e8-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf2" src="1f1f0-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddec" src="1f1e8-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udde9" src="1f1e8-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf0" src="1f1e8-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddf7" src="1f1e8-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf7" src="1f1ed-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfa" src="1f1e8-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfc" src="1f1e8-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddfe" src="1f1e8-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddff" src="1f1e8-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf0" src="1f1e9-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddef" src="1f1e9-1f1ef.png"/>',fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf2" src="1f1e9-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddf4" src="1f1e9-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\udde8" src="1f1ea-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddec" src="1f1ea-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfb" src="1f1f8-1f1fb.png"/>',fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf6" src="1f1ec-1f1f6.png"/>',fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf7" src="1f1ea-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddea" src="1f1ea-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf9" src="1f1ea-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddfa" src="1f1ea-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf0" src="1f1eb-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf4" src="1f1eb-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddef" src="1f1eb-1f1ef.png"/>',fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddee" src="1f1eb-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf7" src="1f1eb-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddeb" src="1f1ec-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddeb" src="1f1f5-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddeb" src="1f1f9-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde6" src="1f1ec-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf2" src="1f1ec-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddea" src="1f1ec-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde9\ud83c\uddea" src="1f1e9-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udded" src="1f1ec-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddee" src="1f1ec-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf7" src="1f1ec-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf1" src="1f1ec-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde9" src="1f1ec-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf5" src="1f1ec-1f1f5.png"/>',fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfa" src="1f1ec-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf9" src="1f1ec-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddec" src="1f1ec-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf3" src="1f1ec-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfc" src="1f1ec-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddfe" src="1f1ec-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf9" src="1f1ed-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf3" src="1f1ed-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddf0" src="1f1ed-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udded\ud83c\uddfa" src="1f1ed-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf8" src="1f1ee-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf3" src="1f1ee-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\udde9" src="1f1ee-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf7" src="1f1ee-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf6" src="1f1ee-1f1f6.png"/>',fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddea" src="1f1ee-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf2" src="1f1ee-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf1" src="1f1ee-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddee\ud83c\uddf9" src="1f1ee-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\uddee" src="1f1e8-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf2" src="1f1ef-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf5" src="1f1ef-1f1f5.png"/>',fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddea" src="1f1ef-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddef\ud83c\uddf4" src="1f1ef-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddff" src="1f1f0-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddea" src="1f1f0-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddee" src="1f1f0-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfd\ud83c\uddf0" src="1f1fd-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddfc" src="1f1f0-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddec" src="1f1f0-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde6" src="1f1f1-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfb" src="1f1f1-1f1fb.png"/>',fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde7" src="1f1f1-1f1e7.png"/>',fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf8" src="1f1f1-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf7" src="1f1f1-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfe" src="1f1f1-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddee" src="1f1f1-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf9" src="1f1f1-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddfa" src="1f1f1-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf4" src="1f1f2-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf0" src="1f1f2-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddec" src="1f1f2-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfc" src="1f1f2-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfe" src="1f1f2-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfb" src="1f1f2-1f1fb.png"/>',fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf1" src="1f1f2-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf9" src="1f1f2-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udded" src="1f1f2-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf6" src="1f1f2-1f1f6.png"/>',fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf7" src="1f1f2-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfa" src="1f1f2-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfe\ud83c\uddf9" src="1f1fe-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddfd" src="1f1f2-1f1fd.png"/>',fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddeb\ud83c\uddf2" src="1f1eb-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde9" src="1f1f2-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde8" src="1f1f2-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf3" src="1f1f2-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddea" src="1f1f2-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf8" src="1f1f2-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\udde6" src="1f1f2-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddff" src="1f1f2-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf2" src="1f1f2-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\udde6" src="1f1f3-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf7" src="1f1f3-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf5" src="1f1f3-1f1f5.png"/>',fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf1" src="1f1f3-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\udde8" src="1f1f3-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddff" src="1f1f3-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddee" src="1f1f3-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddea" src="1f1f3-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddec" src="1f1f3-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddfa" src="1f1f3-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddeb" src="1f1f3-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf2\ud83c\uddf5" src="1f1f2-1f1f5.png"/>',fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf5" src="1f1f0-1f1f5.png"/>',fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf3\ud83c\uddf4" src="1f1f3-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf4\ud83c\uddf2" src="1f1f4-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf0" src="1f1f5-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddfc" src="1f1f5-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf8" src="1f1f5-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\udde6" src="1f1f5-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddec" src="1f1f5-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddfe" src="1f1f5-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddea" src="1f1f5-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\udded" src="1f1f5-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf3" src="1f1f5-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf1" src="1f1f5-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf9" src="1f1f5-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf7" src="1f1f5-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf6\ud83c\udde6" src="1f1f6-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddea" src="1f1f7-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddf4" src="1f1f7-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddfa" src="1f1f7-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddfc" src="1f1f7-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde7\ud83c\uddf1" src="1f1e7-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udded" src="1f1f8-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf3" src="1f1f0-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\udde8" src="1f1f1-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf5\ud83c\uddf2" src="1f1f5-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\udde8" src="1f1fb-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfc\ud83c\uddf8" src="1f1fc-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf2" src="1f1f8-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf9" src="1f1f8-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde6" src="1f1f8-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf3" src="1f1f8-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf7\ud83c\uddf8" src="1f1f7-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde8" src="1f1f8-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf1" src="1f1f8-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddec" src="1f1f8-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfd" src="1f1f8-1f1fd.png"/>',fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf0" src="1f1f8-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddee" src="1f1f8-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde7" src="1f1f8-1f1e7.png"/>',fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf4" src="1f1f8-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\udde6" src="1f1ff-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\uddf8" src="1f1ec-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf0\ud83c\uddf7" src="1f1f0-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf8" src="1f1f8-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\uddf8" src="1f1ea-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf1\ud83c\uddf0" src="1f1f1-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\udde9" src="1f1f8-1f1e9.png"/>',fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddf7" src="1f1f8-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddff" src="1f1f8-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddea" src="1f1f8-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde8\ud83c\udded" src="1f1e8-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf8\ud83c\uddfe" src="1f1f8-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddfc" src="1f1f9-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddef" src="1f1f9-1f1ef.png"/>',fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddff" src="1f1f9-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udded" src="1f1f9-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf1" src="1f1f9-1f1f1.png"/>',fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddec" src="1f1f9-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf0" src="1f1f9-1f1f0.png"/>',fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf4" src="1f1f9-1f1f4.png"/>',fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf9" src="1f1f9-1f1f9.png"/>',fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf3" src="1f1f9-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf7" src="1f1f9-1f1f7.png"/>',fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddf2" src="1f1f9-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\udde8" src="1f1f9-1f1e8.png"/>',fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddf9\ud83c\uddfb" src="1f1f9-1f1fb.png"/>',fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddec" src="1f1fa-1f1ec.png"/>',fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\udde6" src="1f1fa-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udde6\ud83c\uddea" src="1f1e6-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddec\ud83c\udde7" src="1f1ec-1f1e7.png"/>',fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f" src="1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png"/>',fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f" src="1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png"/>',fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f" src="1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png"/>',fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddf8" src="1f1fa-1f1f8.png"/>',fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddee" src="1f1fb-1f1ee.png"/>',fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddfe" src="1f1fa-1f1fe.png"/>',fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddff" src="1f1fa-1f1ff.png"/>',fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddfa" src="1f1fb-1f1fa.png"/>',fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\udde6" src="1f1fb-1f1e6.png"/>',fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddea" src="1f1fb-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfb\ud83c\uddf3" src="1f1fb-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfc\ud83c\uddeb" src="1f1fc-1f1eb.png"/>',fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddea\ud83c\udded" src="1f1ea-1f1ed.png"/>',fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfe\ud83c\uddea" src="1f1fe-1f1ea.png"/>',fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\uddf2" src="1f1ff-1f1f2.png"/>',fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddff\ud83c\uddfc" src="1f1ff-1f1fc.png"/>',fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\uddfa\ud83c\uddf3" src="1f1fa-1f1f3.png"/>',fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'<img data-emoticon="true" style="width:1em;height:1em;margin:0 .05em 0 .1em;vertical-align:-.1em" draggable="false" alt="\ud83c\udff4\u200d\u2620\ufe0f" src="1f3f4-200d-2620-fe0f.png"/>',fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/emoticons/js/emojis.js b/apps/st/public/tinymce/plugins/emoticons/js/emojis.js new file mode 100644 index 000000000..cf7f472bf --- /dev/null +++ b/apps/st/public/tinymce/plugins/emoticons/js/emojis.js @@ -0,0 +1,9423 @@ +// Source: npm package: emojilib, file:emojis.json +window.tinymce.Resource.add("tinymce.plugins.emoticons", { + grinning: { + keywords: [ "face", "smile", "happy", "joy", ":D", "grin" ], + char: "\ud83d\ude00", + fitzpatrick_scale: false, + category: "people" + }, + grimacing: { + keywords: [ "face", "grimace", "teeth" ], + char: "\ud83d\ude2c", + fitzpatrick_scale: false, + category: "people" + }, + grin: { + keywords: [ "face", "happy", "smile", "joy", "kawaii" ], + char: "\ud83d\ude01", + fitzpatrick_scale: false, + category: "people" + }, + joy: { + keywords: [ "face", "cry", "tears", "weep", "happy", "happytears", "haha" ], + char: "\ud83d\ude02", + fitzpatrick_scale: false, + category: "people" + }, + rofl: { + keywords: [ "face", "rolling", "floor", "laughing", "lol", "haha" ], + char: "\ud83e\udd23", + fitzpatrick_scale: false, + category: "people" + }, + partying: { + keywords: [ "face", "celebration", "woohoo" ], + char: "\ud83e\udd73", + fitzpatrick_scale: false, + category: "people" + }, + smiley: { + keywords: [ "face", "happy", "joy", "haha", ":D", ":)", "smile", "funny" ], + char: "\ud83d\ude03", + fitzpatrick_scale: false, + category: "people" + }, + smile: { + keywords: [ "face", "happy", "joy", "funny", "haha", "laugh", "like", ":D", ":)" ], + char: "\ud83d\ude04", + fitzpatrick_scale: false, + category: "people" + }, + sweat_smile: { + keywords: [ "face", "hot", "happy", "laugh", "sweat", "smile", "relief" ], + char: "\ud83d\ude05", + fitzpatrick_scale: false, + category: "people" + }, + laughing: { + keywords: [ "happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh" ], + char: "\ud83d\ude06", + fitzpatrick_scale: false, + category: "people" + }, + innocent: { + keywords: [ "face", "angel", "heaven", "halo" ], + char: "\ud83d\ude07", + fitzpatrick_scale: false, + category: "people" + }, + wink: { + keywords: [ "face", "happy", "mischievous", "secret", ";)", "smile", "eye" ], + char: "\ud83d\ude09", + fitzpatrick_scale: false, + category: "people" + }, + blush: { + keywords: [ "face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy" ], + char: "\ud83d\ude0a", + fitzpatrick_scale: false, + category: "people" + }, + slightly_smiling_face: { + keywords: [ "face", "smile" ], + char: "\ud83d\ude42", + fitzpatrick_scale: false, + category: "people" + }, + upside_down_face: { + keywords: [ "face", "flipped", "silly", "smile" ], + char: "\ud83d\ude43", + fitzpatrick_scale: false, + category: "people" + }, + relaxed: { + keywords: [ "face", "blush", "massage", "happiness" ], + char: "\u263a\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + yum: { + keywords: [ "happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring" ], + char: "\ud83d\ude0b", + fitzpatrick_scale: false, + category: "people" + }, + relieved: { + keywords: [ "face", "relaxed", "phew", "massage", "happiness" ], + char: "\ud83d\ude0c", + fitzpatrick_scale: false, + category: "people" + }, + heart_eyes: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart" ], + char: "\ud83d\ude0d", + fitzpatrick_scale: false, + category: "people" + }, + smiling_face_with_three_hearts: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore" ], + char: "\ud83e\udd70", + fitzpatrick_scale: false, + category: "people" + }, + kissing_heart: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "kiss" ], + char: "\ud83d\ude18", + fitzpatrick_scale: false, + category: "people" + }, + kissing: { + keywords: [ "love", "like", "face", "3", "valentines", "infatuation", "kiss" ], + char: "\ud83d\ude17", + fitzpatrick_scale: false, + category: "people" + }, + kissing_smiling_eyes: { + keywords: [ "face", "affection", "valentines", "infatuation", "kiss" ], + char: "\ud83d\ude19", + fitzpatrick_scale: false, + category: "people" + }, + kissing_closed_eyes: { + keywords: [ "face", "love", "like", "affection", "valentines", "infatuation", "kiss" ], + char: "\ud83d\ude1a", + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue_winking_eye: { + keywords: [ "face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue" ], + char: "\ud83d\ude1c", + fitzpatrick_scale: false, + category: "people" + }, + zany: { + keywords: [ "face", "goofy", "crazy" ], + char: "\ud83e\udd2a", + fitzpatrick_scale: false, + category: "people" + }, + raised_eyebrow: { + keywords: [ "face", "distrust", "scepticism", "disapproval", "disbelief", "surprise" ], + char: "\ud83e\udd28", + fitzpatrick_scale: false, + category: "people" + }, + monocle: { + keywords: [ "face", "stuffy", "wealthy" ], + char: "\ud83e\uddd0", + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue_closed_eyes: { + keywords: [ "face", "prank", "playful", "mischievous", "smile", "tongue" ], + char: "\ud83d\ude1d", + fitzpatrick_scale: false, + category: "people" + }, + stuck_out_tongue: { + keywords: [ "face", "prank", "childish", "playful", "mischievous", "smile", "tongue" ], + char: "\ud83d\ude1b", + fitzpatrick_scale: false, + category: "people" + }, + money_mouth_face: { + keywords: [ "face", "rich", "dollar", "money" ], + char: "\ud83e\udd11", + fitzpatrick_scale: false, + category: "people" + }, + nerd_face: { + keywords: [ "face", "nerdy", "geek", "dork" ], + char: "\ud83e\udd13", + fitzpatrick_scale: false, + category: "people" + }, + sunglasses: { + keywords: [ "face", "cool", "smile", "summer", "beach", "sunglass" ], + char: "\ud83d\ude0e", + fitzpatrick_scale: false, + category: "people" + }, + star_struck: { + keywords: [ "face", "smile", "starry", "eyes", "grinning" ], + char: "\ud83e\udd29", + fitzpatrick_scale: false, + category: "people" + }, + clown_face: { + keywords: [ "face" ], + char: "\ud83e\udd21", + fitzpatrick_scale: false, + category: "people" + }, + cowboy_hat_face: { + keywords: [ "face", "cowgirl", "hat" ], + char: "\ud83e\udd20", + fitzpatrick_scale: false, + category: "people" + }, + hugs: { + keywords: [ "face", "smile", "hug" ], + char: "\ud83e\udd17", + fitzpatrick_scale: false, + category: "people" + }, + smirk: { + keywords: [ "face", "smile", "mean", "prank", "smug", "sarcasm" ], + char: "\ud83d\ude0f", + fitzpatrick_scale: false, + category: "people" + }, + no_mouth: { + keywords: [ "face", "hellokitty" ], + char: "\ud83d\ude36", + fitzpatrick_scale: false, + category: "people" + }, + neutral_face: { + keywords: [ "indifference", "meh", ":|", "neutral" ], + char: "\ud83d\ude10", + fitzpatrick_scale: false, + category: "people" + }, + expressionless: { + keywords: [ "face", "indifferent", "-_-", "meh", "deadpan" ], + char: "\ud83d\ude11", + fitzpatrick_scale: false, + category: "people" + }, + unamused: { + keywords: [ "indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye" ], + char: "\ud83d\ude12", + fitzpatrick_scale: false, + category: "people" + }, + roll_eyes: { + keywords: [ "face", "eyeroll", "frustrated" ], + char: "\ud83d\ude44", + fitzpatrick_scale: false, + category: "people" + }, + thinking: { + keywords: [ "face", "hmmm", "think", "consider" ], + char: "\ud83e\udd14", + fitzpatrick_scale: false, + category: "people" + }, + lying_face: { + keywords: [ "face", "lie", "pinocchio" ], + char: "\ud83e\udd25", + fitzpatrick_scale: false, + category: "people" + }, + hand_over_mouth: { + keywords: [ "face", "whoops", "shock", "surprise" ], + char: "\ud83e\udd2d", + fitzpatrick_scale: false, + category: "people" + }, + shushing: { + keywords: [ "face", "quiet", "shhh" ], + char: "\ud83e\udd2b", + fitzpatrick_scale: false, + category: "people" + }, + symbols_over_mouth: { + keywords: [ "face", "swearing", "cursing", "cussing", "profanity", "expletive" ], + char: "\ud83e\udd2c", + fitzpatrick_scale: false, + category: "people" + }, + exploding_head: { + keywords: [ "face", "shocked", "mind", "blown" ], + char: "\ud83e\udd2f", + fitzpatrick_scale: false, + category: "people" + }, + flushed: { + keywords: [ "face", "blush", "shy", "flattered" ], + char: "\ud83d\ude33", + fitzpatrick_scale: false, + category: "people" + }, + disappointed: { + keywords: [ "face", "sad", "upset", "depressed", ":(" ], + char: "\ud83d\ude1e", + fitzpatrick_scale: false, + category: "people" + }, + worried: { + keywords: [ "face", "concern", "nervous", ":(" ], + char: "\ud83d\ude1f", + fitzpatrick_scale: false, + category: "people" + }, + angry: { + keywords: [ "mad", "face", "annoyed", "frustrated" ], + char: "\ud83d\ude20", + fitzpatrick_scale: false, + category: "people" + }, + rage: { + keywords: [ "angry", "mad", "hate", "despise" ], + char: "\ud83d\ude21", + fitzpatrick_scale: false, + category: "people" + }, + pensive: { + keywords: [ "face", "sad", "depressed", "upset" ], + char: "\ud83d\ude14", + fitzpatrick_scale: false, + category: "people" + }, + confused: { + keywords: [ "face", "indifference", "huh", "weird", "hmmm", ":/" ], + char: "\ud83d\ude15", + fitzpatrick_scale: false, + category: "people" + }, + slightly_frowning_face: { + keywords: [ "face", "frowning", "disappointed", "sad", "upset" ], + char: "\ud83d\ude41", + fitzpatrick_scale: false, + category: "people" + }, + frowning_face: { + keywords: [ "face", "sad", "upset", "frown" ], + char: "\u2639", + fitzpatrick_scale: false, + category: "people" + }, + persevere: { + keywords: [ "face", "sick", "no", "upset", "oops" ], + char: "\ud83d\ude23", + fitzpatrick_scale: false, + category: "people" + }, + confounded: { + keywords: [ "face", "confused", "sick", "unwell", "oops", ":S" ], + char: "\ud83d\ude16", + fitzpatrick_scale: false, + category: "people" + }, + tired_face: { + keywords: [ "sick", "whine", "upset", "frustrated" ], + char: "\ud83d\ude2b", + fitzpatrick_scale: false, + category: "people" + }, + weary: { + keywords: [ "face", "tired", "sleepy", "sad", "frustrated", "upset" ], + char: "\ud83d\ude29", + fitzpatrick_scale: false, + category: "people" + }, + pleading: { + keywords: [ "face", "begging", "mercy" ], + char: "\ud83e\udd7a", + fitzpatrick_scale: false, + category: "people" + }, + triumph: { + keywords: [ "face", "gas", "phew", "proud", "pride" ], + char: "\ud83d\ude24", + fitzpatrick_scale: false, + category: "people" + }, + open_mouth: { + keywords: [ "face", "surprise", "impressed", "wow", "whoa", ":O" ], + char: "\ud83d\ude2e", + fitzpatrick_scale: false, + category: "people" + }, + scream: { + keywords: [ "face", "munch", "scared", "omg" ], + char: "\ud83d\ude31", + fitzpatrick_scale: false, + category: "people" + }, + fearful: { + keywords: [ "face", "scared", "terrified", "nervous", "oops", "huh" ], + char: "\ud83d\ude28", + fitzpatrick_scale: false, + category: "people" + }, + cold_sweat: { + keywords: [ "face", "nervous", "sweat" ], + char: "\ud83d\ude30", + fitzpatrick_scale: false, + category: "people" + }, + hushed: { + keywords: [ "face", "woo", "shh" ], + char: "\ud83d\ude2f", + fitzpatrick_scale: false, + category: "people" + }, + frowning: { + keywords: [ "face", "aw", "what" ], + char: "\ud83d\ude26", + fitzpatrick_scale: false, + category: "people" + }, + anguished: { + keywords: [ "face", "stunned", "nervous" ], + char: "\ud83d\ude27", + fitzpatrick_scale: false, + category: "people" + }, + cry: { + keywords: [ "face", "tears", "sad", "depressed", "upset", ":'(" ], + char: "\ud83d\ude22", + fitzpatrick_scale: false, + category: "people" + }, + disappointed_relieved: { + keywords: [ "face", "phew", "sweat", "nervous" ], + char: "\ud83d\ude25", + fitzpatrick_scale: false, + category: "people" + }, + drooling_face: { + keywords: [ "face" ], + char: "\ud83e\udd24", + fitzpatrick_scale: false, + category: "people" + }, + sleepy: { + keywords: [ "face", "tired", "rest", "nap" ], + char: "\ud83d\ude2a", + fitzpatrick_scale: false, + category: "people" + }, + sweat: { + keywords: [ "face", "hot", "sad", "tired", "exercise" ], + char: "\ud83d\ude13", + fitzpatrick_scale: false, + category: "people" + }, + hot: { + keywords: [ "face", "feverish", "heat", "red", "sweating" ], + char: "\ud83e\udd75", + fitzpatrick_scale: false, + category: "people" + }, + cold: { + keywords: [ "face", "blue", "freezing", "frozen", "frostbite", "icicles" ], + char: "\ud83e\udd76", + fitzpatrick_scale: false, + category: "people" + }, + sob: { + keywords: [ "face", "cry", "tears", "sad", "upset", "depressed" ], + char: "\ud83d\ude2d", + fitzpatrick_scale: false, + category: "people" + }, + dizzy_face: { + keywords: [ "spent", "unconscious", "xox", "dizzy" ], + char: "\ud83d\ude35", + fitzpatrick_scale: false, + category: "people" + }, + astonished: { + keywords: [ "face", "xox", "surprised", "poisoned" ], + char: "\ud83d\ude32", + fitzpatrick_scale: false, + category: "people" + }, + zipper_mouth_face: { + keywords: [ "face", "sealed", "zipper", "secret" ], + char: "\ud83e\udd10", + fitzpatrick_scale: false, + category: "people" + }, + nauseated_face: { + keywords: [ "face", "vomit", "gross", "green", "sick", "throw up", "ill" ], + char: "\ud83e\udd22", + fitzpatrick_scale: false, + category: "people" + }, + sneezing_face: { + keywords: [ "face", "gesundheit", "sneeze", "sick", "allergy" ], + char: "\ud83e\udd27", + fitzpatrick_scale: false, + category: "people" + }, + vomiting: { + keywords: [ "face", "sick" ], + char: "\ud83e\udd2e", + fitzpatrick_scale: false, + category: "people" + }, + mask: { + keywords: [ "face", "sick", "ill", "disease" ], + char: "\ud83d\ude37", + fitzpatrick_scale: false, + category: "people" + }, + face_with_thermometer: { + keywords: [ "sick", "temperature", "thermometer", "cold", "fever" ], + char: "\ud83e\udd12", + fitzpatrick_scale: false, + category: "people" + }, + face_with_head_bandage: { + keywords: [ "injured", "clumsy", "bandage", "hurt" ], + char: "\ud83e\udd15", + fitzpatrick_scale: false, + category: "people" + }, + woozy: { + keywords: [ "face", "dizzy", "intoxicated", "tipsy", "wavy" ], + char: "\ud83e\udd74", + fitzpatrick_scale: false, + category: "people" + }, + sleeping: { + keywords: [ "face", "tired", "sleepy", "night", "zzz" ], + char: "\ud83d\ude34", + fitzpatrick_scale: false, + category: "people" + }, + zzz: { + keywords: [ "sleepy", "tired", "dream" ], + char: "\ud83d\udca4", + fitzpatrick_scale: false, + category: "people" + }, + poop: { + keywords: [ "hankey", "shitface", "fail", "turd", "shit" ], + char: "\ud83d\udca9", + fitzpatrick_scale: false, + category: "people" + }, + smiling_imp: { + keywords: [ "devil", "horns" ], + char: "\ud83d\ude08", + fitzpatrick_scale: false, + category: "people" + }, + imp: { + keywords: [ "devil", "angry", "horns" ], + char: "\ud83d\udc7f", + fitzpatrick_scale: false, + category: "people" + }, + japanese_ogre: { + keywords: [ "monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre" ], + char: "\ud83d\udc79", + fitzpatrick_scale: false, + category: "people" + }, + japanese_goblin: { + keywords: [ "red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin" ], + char: "\ud83d\udc7a", + fitzpatrick_scale: false, + category: "people" + }, + skull: { + keywords: [ "dead", "skeleton", "creepy", "death" ], + char: "\ud83d\udc80", + fitzpatrick_scale: false, + category: "people" + }, + ghost: { + keywords: [ "halloween", "spooky", "scary" ], + char: "\ud83d\udc7b", + fitzpatrick_scale: false, + category: "people" + }, + alien: { + keywords: [ "UFO", "paul", "weird", "outer_space" ], + char: "\ud83d\udc7d", + fitzpatrick_scale: false, + category: "people" + }, + robot: { + keywords: [ "computer", "machine", "bot" ], + char: "\ud83e\udd16", + fitzpatrick_scale: false, + category: "people" + }, + smiley_cat: { + keywords: [ "animal", "cats", "happy", "smile" ], + char: "\ud83d\ude3a", + fitzpatrick_scale: false, + category: "people" + }, + smile_cat: { + keywords: [ "animal", "cats", "smile" ], + char: "\ud83d\ude38", + fitzpatrick_scale: false, + category: "people" + }, + joy_cat: { + keywords: [ "animal", "cats", "haha", "happy", "tears" ], + char: "\ud83d\ude39", + fitzpatrick_scale: false, + category: "people" + }, + heart_eyes_cat: { + keywords: [ "animal", "love", "like", "affection", "cats", "valentines", "heart" ], + char: "\ud83d\ude3b", + fitzpatrick_scale: false, + category: "people" + }, + smirk_cat: { + keywords: [ "animal", "cats", "smirk" ], + char: "\ud83d\ude3c", + fitzpatrick_scale: false, + category: "people" + }, + kissing_cat: { + keywords: [ "animal", "cats", "kiss" ], + char: "\ud83d\ude3d", + fitzpatrick_scale: false, + category: "people" + }, + scream_cat: { + keywords: [ "animal", "cats", "munch", "scared", "scream" ], + char: "\ud83d\ude40", + fitzpatrick_scale: false, + category: "people" + }, + crying_cat_face: { + keywords: [ "animal", "tears", "weep", "sad", "cats", "upset", "cry" ], + char: "\ud83d\ude3f", + fitzpatrick_scale: false, + category: "people" + }, + pouting_cat: { + keywords: [ "animal", "cats" ], + char: "\ud83d\ude3e", + fitzpatrick_scale: false, + category: "people" + }, + palms_up: { + keywords: [ "hands", "gesture", "cupped", "prayer" ], + char: "\ud83e\udd32", + fitzpatrick_scale: true, + category: "people" + }, + raised_hands: { + keywords: [ "gesture", "hooray", "yea", "celebration", "hands" ], + char: "\ud83d\ude4c", + fitzpatrick_scale: true, + category: "people" + }, + clap: { + keywords: [ "hands", "praise", "applause", "congrats", "yay" ], + char: "\ud83d\udc4f", + fitzpatrick_scale: true, + category: "people" + }, + wave: { + keywords: [ "hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm" ], + char: "\ud83d\udc4b", + fitzpatrick_scale: true, + category: "people" + }, + call_me_hand: { + keywords: [ "hands", "gesture" ], + char: "\ud83e\udd19", + fitzpatrick_scale: true, + category: "people" + }, + "+1": { + keywords: [ "thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like" ], + char: "\ud83d\udc4d", + fitzpatrick_scale: true, + category: "people" + }, + "-1": { + keywords: [ "thumbsdown", "no", "dislike", "hand" ], + char: "\ud83d\udc4e", + fitzpatrick_scale: true, + category: "people" + }, + facepunch: { + keywords: [ "angry", "violence", "fist", "hit", "attack", "hand" ], + char: "\ud83d\udc4a", + fitzpatrick_scale: true, + category: "people" + }, + fist: { + keywords: [ "fingers", "hand", "grasp" ], + char: "\u270a", + fitzpatrick_scale: true, + category: "people" + }, + fist_left: { + keywords: [ "hand", "fistbump" ], + char: "\ud83e\udd1b", + fitzpatrick_scale: true, + category: "people" + }, + fist_right: { + keywords: [ "hand", "fistbump" ], + char: "\ud83e\udd1c", + fitzpatrick_scale: true, + category: "people" + }, + v: { + keywords: [ "fingers", "ohyeah", "hand", "peace", "victory", "two" ], + char: "\u270c", + fitzpatrick_scale: true, + category: "people" + }, + ok_hand: { + keywords: [ "fingers", "limbs", "perfect", "ok", "okay" ], + char: "\ud83d\udc4c", + fitzpatrick_scale: true, + category: "people" + }, + raised_hand: { + keywords: [ "fingers", "stop", "highfive", "palm", "ban" ], + char: "\u270b", + fitzpatrick_scale: true, + category: "people" + }, + raised_back_of_hand: { + keywords: [ "fingers", "raised", "backhand" ], + char: "\ud83e\udd1a", + fitzpatrick_scale: true, + category: "people" + }, + open_hands: { + keywords: [ "fingers", "butterfly", "hands", "open" ], + char: "\ud83d\udc50", + fitzpatrick_scale: true, + category: "people" + }, + muscle: { + keywords: [ "arm", "flex", "hand", "summer", "strong", "biceps" ], + char: "\ud83d\udcaa", + fitzpatrick_scale: true, + category: "people" + }, + pray: { + keywords: [ "please", "hope", "wish", "namaste", "highfive" ], + char: "\ud83d\ude4f", + fitzpatrick_scale: true, + category: "people" + }, + foot: { + keywords: [ "kick", "stomp" ], + char: "\ud83e\uddb6", + fitzpatrick_scale: true, + category: "people" + }, + leg: { + keywords: [ "kick", "limb" ], + char: "\ud83e\uddb5", + fitzpatrick_scale: true, + category: "people" + }, + handshake: { + keywords: [ "agreement", "shake" ], + char: "\ud83e\udd1d", + fitzpatrick_scale: false, + category: "people" + }, + point_up: { + keywords: [ "hand", "fingers", "direction", "up" ], + char: "\u261d", + fitzpatrick_scale: true, + category: "people" + }, + point_up_2: { + keywords: [ "fingers", "hand", "direction", "up" ], + char: "\ud83d\udc46", + fitzpatrick_scale: true, + category: "people" + }, + point_down: { + keywords: [ "fingers", "hand", "direction", "down" ], + char: "\ud83d\udc47", + fitzpatrick_scale: true, + category: "people" + }, + point_left: { + keywords: [ "direction", "fingers", "hand", "left" ], + char: "\ud83d\udc48", + fitzpatrick_scale: true, + category: "people" + }, + point_right: { + keywords: [ "fingers", "hand", "direction", "right" ], + char: "\ud83d\udc49", + fitzpatrick_scale: true, + category: "people" + }, + fu: { + keywords: [ "hand", "fingers", "rude", "middle", "flipping" ], + char: "\ud83d\udd95", + fitzpatrick_scale: true, + category: "people" + }, + raised_hand_with_fingers_splayed: { + keywords: [ "hand", "fingers", "palm" ], + char: "\ud83d\udd90", + fitzpatrick_scale: true, + category: "people" + }, + love_you: { + keywords: [ "hand", "fingers", "gesture" ], + char: "\ud83e\udd1f", + fitzpatrick_scale: true, + category: "people" + }, + metal: { + keywords: [ "hand", "fingers", "evil_eye", "sign_of_horns", "rock_on" ], + char: "\ud83e\udd18", + fitzpatrick_scale: true, + category: "people" + }, + crossed_fingers: { + keywords: [ "good", "lucky" ], + char: "\ud83e\udd1e", + fitzpatrick_scale: true, + category: "people" + }, + vulcan_salute: { + keywords: [ "hand", "fingers", "spock", "star trek" ], + char: "\ud83d\udd96", + fitzpatrick_scale: true, + category: "people" + }, + writing_hand: { + keywords: [ "lower_left_ballpoint_pen", "stationery", "write", "compose" ], + char: "\u270d", + fitzpatrick_scale: true, + category: "people" + }, + selfie: { + keywords: [ "camera", "phone" ], + char: "\ud83e\udd33", + fitzpatrick_scale: true, + category: "people" + }, + nail_care: { + keywords: [ "beauty", "manicure", "finger", "fashion", "nail" ], + char: "\ud83d\udc85", + fitzpatrick_scale: true, + category: "people" + }, + lips: { + keywords: [ "mouth", "kiss" ], + char: "\ud83d\udc44", + fitzpatrick_scale: false, + category: "people" + }, + tooth: { + keywords: [ "teeth", "dentist" ], + char: "\ud83e\uddb7", + fitzpatrick_scale: false, + category: "people" + }, + tongue: { + keywords: [ "mouth", "playful" ], + char: "\ud83d\udc45", + fitzpatrick_scale: false, + category: "people" + }, + ear: { + keywords: [ "face", "hear", "sound", "listen" ], + char: "\ud83d\udc42", + fitzpatrick_scale: true, + category: "people" + }, + nose: { + keywords: [ "smell", "sniff" ], + char: "\ud83d\udc43", + fitzpatrick_scale: true, + category: "people" + }, + eye: { + keywords: [ "face", "look", "see", "watch", "stare" ], + char: "\ud83d\udc41", + fitzpatrick_scale: false, + category: "people" + }, + eyes: { + keywords: [ "look", "watch", "stalk", "peek", "see" ], + char: "\ud83d\udc40", + fitzpatrick_scale: false, + category: "people" + }, + brain: { + keywords: [ "smart", "intelligent" ], + char: "\ud83e\udde0", + fitzpatrick_scale: false, + category: "people" + }, + bust_in_silhouette: { + keywords: [ "user", "person", "human" ], + char: "\ud83d\udc64", + fitzpatrick_scale: false, + category: "people" + }, + busts_in_silhouette: { + keywords: [ "user", "person", "human", "group", "team" ], + char: "\ud83d\udc65", + fitzpatrick_scale: false, + category: "people" + }, + speaking_head: { + keywords: [ "user", "person", "human", "sing", "say", "talk" ], + char: "\ud83d\udde3", + fitzpatrick_scale: false, + category: "people" + }, + baby: { + keywords: [ "child", "boy", "girl", "toddler" ], + char: "\ud83d\udc76", + fitzpatrick_scale: true, + category: "people" + }, + child: { + keywords: [ "gender-neutral", "young" ], + char: "\ud83e\uddd2", + fitzpatrick_scale: true, + category: "people" + }, + boy: { + keywords: [ "man", "male", "guy", "teenager" ], + char: "\ud83d\udc66", + fitzpatrick_scale: true, + category: "people" + }, + girl: { + keywords: [ "female", "woman", "teenager" ], + char: "\ud83d\udc67", + fitzpatrick_scale: true, + category: "people" + }, + adult: { + keywords: [ "gender-neutral", "person" ], + char: "\ud83e\uddd1", + fitzpatrick_scale: true, + category: "people" + }, + man: { + keywords: [ "mustache", "father", "dad", "guy", "classy", "sir", "moustache" ], + char: "\ud83d\udc68", + fitzpatrick_scale: true, + category: "people" + }, + woman: { + keywords: [ "female", "girls", "lady" ], + char: "\ud83d\udc69", + fitzpatrick_scale: true, + category: "people" + }, + blonde_woman: { + keywords: [ "woman", "female", "girl", "blonde", "person" ], + char: "\ud83d\udc71\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + blonde_man: { + keywords: [ "man", "male", "boy", "blonde", "guy", "person" ], + char: "\ud83d\udc71", + fitzpatrick_scale: true, + category: "people" + }, + bearded_person: { + keywords: [ "person", "bewhiskered" ], + char: "\ud83e\uddd4", + fitzpatrick_scale: true, + category: "people" + }, + older_adult: { + keywords: [ "human", "elder", "senior", "gender-neutral" ], + char: "\ud83e\uddd3", + fitzpatrick_scale: true, + category: "people" + }, + older_man: { + keywords: [ "human", "male", "men", "old", "elder", "senior" ], + char: "\ud83d\udc74", + fitzpatrick_scale: true, + category: "people" + }, + older_woman: { + keywords: [ "human", "female", "women", "lady", "old", "elder", "senior" ], + char: "\ud83d\udc75", + fitzpatrick_scale: true, + category: "people" + }, + man_with_gua_pi_mao: { + keywords: [ "male", "boy", "chinese" ], + char: "\ud83d\udc72", + fitzpatrick_scale: true, + category: "people" + }, + woman_with_headscarf: { + keywords: [ "female", "hijab", "mantilla", "tichel" ], + char: "\ud83e\uddd5", + fitzpatrick_scale: true, + category: "people" + }, + woman_with_turban: { + keywords: [ "female", "indian", "hinduism", "arabs", "woman" ], + char: "\ud83d\udc73\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_with_turban: { + keywords: [ "male", "indian", "hinduism", "arabs" ], + char: "\ud83d\udc73", + fitzpatrick_scale: true, + category: "people" + }, + policewoman: { + keywords: [ "woman", "police", "law", "legal", "enforcement", "arrest", "911", "female" ], + char: "\ud83d\udc6e\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + policeman: { + keywords: [ "man", "police", "law", "legal", "enforcement", "arrest", "911" ], + char: "\ud83d\udc6e", + fitzpatrick_scale: true, + category: "people" + }, + construction_worker_woman: { + keywords: [ "female", "human", "wip", "build", "construction", "worker", "labor", "woman" ], + char: "\ud83d\udc77\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + construction_worker_man: { + keywords: [ "male", "human", "wip", "guy", "build", "construction", "worker", "labor" ], + char: "\ud83d\udc77", + fitzpatrick_scale: true, + category: "people" + }, + guardswoman: { + keywords: [ "uk", "gb", "british", "female", "royal", "woman" ], + char: "\ud83d\udc82\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + guardsman: { + keywords: [ "uk", "gb", "british", "male", "guy", "royal" ], + char: "\ud83d\udc82", + fitzpatrick_scale: true, + category: "people" + }, + female_detective: { + keywords: [ "human", "spy", "detective", "female", "woman" ], + char: "\ud83d\udd75\ufe0f\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + male_detective: { + keywords: [ "human", "spy", "detective" ], + char: "\ud83d\udd75", + fitzpatrick_scale: true, + category: "people" + }, + woman_health_worker: { + keywords: [ "doctor", "nurse", "therapist", "healthcare", "woman", "human" ], + char: "\ud83d\udc69\u200d\u2695\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_health_worker: { + keywords: [ "doctor", "nurse", "therapist", "healthcare", "man", "human" ], + char: "\ud83d\udc68\u200d\u2695\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_farmer: { + keywords: [ "rancher", "gardener", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udf3e", + fitzpatrick_scale: true, + category: "people" + }, + man_farmer: { + keywords: [ "rancher", "gardener", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udf3e", + fitzpatrick_scale: true, + category: "people" + }, + woman_cook: { + keywords: [ "chef", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udf73", + fitzpatrick_scale: true, + category: "people" + }, + man_cook: { + keywords: [ "chef", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udf73", + fitzpatrick_scale: true, + category: "people" + }, + woman_student: { + keywords: [ "graduate", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udf93", + fitzpatrick_scale: true, + category: "people" + }, + man_student: { + keywords: [ "graduate", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udf93", + fitzpatrick_scale: true, + category: "people" + }, + woman_singer: { + keywords: [ "rockstar", "entertainer", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udfa4", + fitzpatrick_scale: true, + category: "people" + }, + man_singer: { + keywords: [ "rockstar", "entertainer", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udfa4", + fitzpatrick_scale: true, + category: "people" + }, + woman_teacher: { + keywords: [ "instructor", "professor", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udfeb", + fitzpatrick_scale: true, + category: "people" + }, + man_teacher: { + keywords: [ "instructor", "professor", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udfeb", + fitzpatrick_scale: true, + category: "people" + }, + woman_factory_worker: { + keywords: [ "assembly", "industrial", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udfed", + fitzpatrick_scale: true, + category: "people" + }, + man_factory_worker: { + keywords: [ "assembly", "industrial", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udfed", + fitzpatrick_scale: true, + category: "people" + }, + woman_technologist: { + keywords: [ "coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer" ], + char: "\ud83d\udc69\u200d\ud83d\udcbb", + fitzpatrick_scale: true, + category: "people" + }, + man_technologist: { + keywords: [ "coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer" ], + char: "\ud83d\udc68\u200d\ud83d\udcbb", + fitzpatrick_scale: true, + category: "people" + }, + woman_office_worker: { + keywords: [ "business", "manager", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83d\udcbc", + fitzpatrick_scale: true, + category: "people" + }, + man_office_worker: { + keywords: [ "business", "manager", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83d\udcbc", + fitzpatrick_scale: true, + category: "people" + }, + woman_mechanic: { + keywords: [ "plumber", "woman", "human", "wrench" ], + char: "\ud83d\udc69\u200d\ud83d\udd27", + fitzpatrick_scale: true, + category: "people" + }, + man_mechanic: { + keywords: [ "plumber", "man", "human", "wrench" ], + char: "\ud83d\udc68\u200d\ud83d\udd27", + fitzpatrick_scale: true, + category: "people" + }, + woman_scientist: { + keywords: [ "biologist", "chemist", "engineer", "physicist", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83d\udd2c", + fitzpatrick_scale: true, + category: "people" + }, + man_scientist: { + keywords: [ "biologist", "chemist", "engineer", "physicist", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83d\udd2c", + fitzpatrick_scale: true, + category: "people" + }, + woman_artist: { + keywords: [ "painter", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83c\udfa8", + fitzpatrick_scale: true, + category: "people" + }, + man_artist: { + keywords: [ "painter", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83c\udfa8", + fitzpatrick_scale: true, + category: "people" + }, + woman_firefighter: { + keywords: [ "fireman", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83d\ude92", + fitzpatrick_scale: true, + category: "people" + }, + man_firefighter: { + keywords: [ "fireman", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83d\ude92", + fitzpatrick_scale: true, + category: "people" + }, + woman_pilot: { + keywords: [ "aviator", "plane", "woman", "human" ], + char: "\ud83d\udc69\u200d\u2708\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_pilot: { + keywords: [ "aviator", "plane", "man", "human" ], + char: "\ud83d\udc68\u200d\u2708\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_astronaut: { + keywords: [ "space", "rocket", "woman", "human" ], + char: "\ud83d\udc69\u200d\ud83d\ude80", + fitzpatrick_scale: true, + category: "people" + }, + man_astronaut: { + keywords: [ "space", "rocket", "man", "human" ], + char: "\ud83d\udc68\u200d\ud83d\ude80", + fitzpatrick_scale: true, + category: "people" + }, + woman_judge: { + keywords: [ "justice", "court", "woman", "human" ], + char: "\ud83d\udc69\u200d\u2696\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_judge: { + keywords: [ "justice", "court", "man", "human" ], + char: "\ud83d\udc68\u200d\u2696\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_superhero: { + keywords: [ "woman", "female", "good", "heroine", "superpowers" ], + char: "\ud83e\uddb8\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_superhero: { + keywords: [ "man", "male", "good", "hero", "superpowers" ], + char: "\ud83e\uddb8\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_supervillain: { + keywords: [ "woman", "female", "evil", "bad", "criminal", "heroine", "superpowers" ], + char: "\ud83e\uddb9\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_supervillain: { + keywords: [ "man", "male", "evil", "bad", "criminal", "hero", "superpowers" ], + char: "\ud83e\uddb9\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + mrs_claus: { + keywords: [ "woman", "female", "xmas", "mother christmas" ], + char: "\ud83e\udd36", + fitzpatrick_scale: true, + category: "people" + }, + santa: { + keywords: [ "festival", "man", "male", "xmas", "father christmas" ], + char: "\ud83c\udf85", + fitzpatrick_scale: true, + category: "people" + }, + sorceress: { + keywords: [ "woman", "female", "mage", "witch" ], + char: "\ud83e\uddd9\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + wizard: { + keywords: [ "man", "male", "mage", "sorcerer" ], + char: "\ud83e\uddd9\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_elf: { + keywords: [ "woman", "female" ], + char: "\ud83e\udddd\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_elf: { + keywords: [ "man", "male" ], + char: "\ud83e\udddd\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_vampire: { + keywords: [ "woman", "female" ], + char: "\ud83e\udddb\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_vampire: { + keywords: [ "man", "male", "dracula" ], + char: "\ud83e\udddb\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_zombie: { + keywords: [ "woman", "female", "undead", "walking dead" ], + char: "\ud83e\udddf\u200d\u2640\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + man_zombie: { + keywords: [ "man", "male", "dracula", "undead", "walking dead" ], + char: "\ud83e\udddf\u200d\u2642\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + woman_genie: { + keywords: [ "woman", "female" ], + char: "\ud83e\uddde\u200d\u2640\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + man_genie: { + keywords: [ "man", "male" ], + char: "\ud83e\uddde\u200d\u2642\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + mermaid: { + keywords: [ "woman", "female", "merwoman", "ariel" ], + char: "\ud83e\udddc\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + merman: { + keywords: [ "man", "male", "triton" ], + char: "\ud83e\udddc\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_fairy: { + keywords: [ "woman", "female" ], + char: "\ud83e\uddda\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_fairy: { + keywords: [ "man", "male" ], + char: "\ud83e\uddda\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + angel: { + keywords: [ "heaven", "wings", "halo" ], + char: "\ud83d\udc7c", + fitzpatrick_scale: true, + category: "people" + }, + pregnant_woman: { + keywords: [ "baby" ], + char: "\ud83e\udd30", + fitzpatrick_scale: true, + category: "people" + }, + breastfeeding: { + keywords: [ "nursing", "baby" ], + char: "\ud83e\udd31", + fitzpatrick_scale: true, + category: "people" + }, + princess: { + keywords: [ "girl", "woman", "female", "blond", "crown", "royal", "queen" ], + char: "\ud83d\udc78", + fitzpatrick_scale: true, + category: "people" + }, + prince: { + keywords: [ "boy", "man", "male", "crown", "royal", "king" ], + char: "\ud83e\udd34", + fitzpatrick_scale: true, + category: "people" + }, + bride_with_veil: { + keywords: [ "couple", "marriage", "wedding", "woman", "bride" ], + char: "\ud83d\udc70", + fitzpatrick_scale: true, + category: "people" + }, + man_in_tuxedo: { + keywords: [ "couple", "marriage", "wedding", "groom" ], + char: "\ud83e\udd35", + fitzpatrick_scale: true, + category: "people" + }, + running_woman: { + keywords: [ "woman", "walking", "exercise", "race", "running", "female" ], + char: "\ud83c\udfc3\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + running_man: { + keywords: [ "man", "walking", "exercise", "race", "running" ], + char: "\ud83c\udfc3", + fitzpatrick_scale: true, + category: "people" + }, + walking_woman: { + keywords: [ "human", "feet", "steps", "woman", "female" ], + char: "\ud83d\udeb6\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + walking_man: { + keywords: [ "human", "feet", "steps" ], + char: "\ud83d\udeb6", + fitzpatrick_scale: true, + category: "people" + }, + dancer: { + keywords: [ "female", "girl", "woman", "fun" ], + char: "\ud83d\udc83", + fitzpatrick_scale: true, + category: "people" + }, + man_dancing: { + keywords: [ "male", "boy", "fun", "dancer" ], + char: "\ud83d\udd7a", + fitzpatrick_scale: true, + category: "people" + }, + dancing_women: { + keywords: [ "female", "bunny", "women", "girls" ], + char: "\ud83d\udc6f", + fitzpatrick_scale: false, + category: "people" + }, + dancing_men: { + keywords: [ "male", "bunny", "men", "boys" ], + char: "\ud83d\udc6f\u200d\u2642\ufe0f", + fitzpatrick_scale: false, + category: "people" + }, + couple: { + keywords: [ "pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage" ], + char: "\ud83d\udc6b", + fitzpatrick_scale: false, + category: "people" + }, + two_men_holding_hands: { + keywords: [ "pair", "couple", "love", "like", "bromance", "friendship", "people", "human" ], + char: "\ud83d\udc6c", + fitzpatrick_scale: false, + category: "people" + }, + two_women_holding_hands: { + keywords: [ "pair", "friendship", "couple", "love", "like", "female", "people", "human" ], + char: "\ud83d\udc6d", + fitzpatrick_scale: false, + category: "people" + }, + bowing_woman: { + keywords: [ "woman", "female", "girl" ], + char: "\ud83d\ude47\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + bowing_man: { + keywords: [ "man", "male", "boy" ], + char: "\ud83d\ude47", + fitzpatrick_scale: true, + category: "people" + }, + man_facepalming: { + keywords: [ "man", "male", "boy", "disbelief" ], + char: "\ud83e\udd26\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_facepalming: { + keywords: [ "woman", "female", "girl", "disbelief" ], + char: "\ud83e\udd26\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_shrugging: { + keywords: [ "woman", "female", "girl", "confused", "indifferent", "doubt" ], + char: "\ud83e\udd37", + fitzpatrick_scale: true, + category: "people" + }, + man_shrugging: { + keywords: [ "man", "male", "boy", "confused", "indifferent", "doubt" ], + char: "\ud83e\udd37\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + tipping_hand_woman: { + keywords: [ "female", "girl", "woman", "human", "information" ], + char: "\ud83d\udc81", + fitzpatrick_scale: true, + category: "people" + }, + tipping_hand_man: { + keywords: [ "male", "boy", "man", "human", "information" ], + char: "\ud83d\udc81\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + no_good_woman: { + keywords: [ "female", "girl", "woman", "nope" ], + char: "\ud83d\ude45", + fitzpatrick_scale: true, + category: "people" + }, + no_good_man: { + keywords: [ "male", "boy", "man", "nope" ], + char: "\ud83d\ude45\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + ok_woman: { + keywords: [ "women", "girl", "female", "pink", "human", "woman" ], + char: "\ud83d\ude46", + fitzpatrick_scale: true, + category: "people" + }, + ok_man: { + keywords: [ "men", "boy", "male", "blue", "human", "man" ], + char: "\ud83d\ude46\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + raising_hand_woman: { + keywords: [ "female", "girl", "woman" ], + char: "\ud83d\ude4b", + fitzpatrick_scale: true, + category: "people" + }, + raising_hand_man: { + keywords: [ "male", "boy", "man" ], + char: "\ud83d\ude4b\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + pouting_woman: { + keywords: [ "female", "girl", "woman" ], + char: "\ud83d\ude4e", + fitzpatrick_scale: true, + category: "people" + }, + pouting_man: { + keywords: [ "male", "boy", "man" ], + char: "\ud83d\ude4e\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + frowning_woman: { + keywords: [ "female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy" ], + char: "\ud83d\ude4d", + fitzpatrick_scale: true, + category: "people" + }, + frowning_man: { + keywords: [ "male", "boy", "man", "sad", "depressed", "discouraged", "unhappy" ], + char: "\ud83d\ude4d\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + haircut_woman: { + keywords: [ "female", "girl", "woman" ], + char: "\ud83d\udc87", + fitzpatrick_scale: true, + category: "people" + }, + haircut_man: { + keywords: [ "male", "boy", "man" ], + char: "\ud83d\udc87\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + massage_woman: { + keywords: [ "female", "girl", "woman", "head" ], + char: "\ud83d\udc86", + fitzpatrick_scale: true, + category: "people" + }, + massage_man: { + keywords: [ "male", "boy", "man", "head" ], + char: "\ud83d\udc86\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + woman_in_steamy_room: { + keywords: [ "female", "woman", "spa", "steamroom", "sauna" ], + char: "\ud83e\uddd6\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + man_in_steamy_room: { + keywords: [ "male", "man", "spa", "steamroom", "sauna" ], + char: "\ud83e\uddd6\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "people" + }, + couple_with_heart_woman_man: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: "\ud83d\udc91", + fitzpatrick_scale: false, + category: "people" + }, + couple_with_heart_woman_woman: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc69", + fitzpatrick_scale: false, + category: "people" + }, + couple_with_heart_man_man: { + keywords: [ "pair", "love", "like", "affection", "human", "dating", "valentines", "marriage" ], + char: "\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68", + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_man_woman: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: "\ud83d\udc8f", + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_woman_woman: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: "\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69", + fitzpatrick_scale: false, + category: "people" + }, + couplekiss_man_man: { + keywords: [ "pair", "valentines", "love", "like", "dating", "marriage" ], + char: "\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68", + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_boy: { + keywords: [ "home", "parents", "child", "mom", "dad", "father", "mother", "people", "human" ], + char: "\ud83d\udc6a", + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl: { + keywords: [ "home", "parents", "people", "human", "child" ], + char: "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_woman_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_woman_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_boy_boy: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_man_girl_girl: { + keywords: [ "home", "parents", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_boy: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: "\ud83d\udc69\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: "\ud83d\udc69\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_boy_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_woman_girl_girl: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_man_boy: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: "\ud83d\udc68\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl: { + keywords: [ "home", "parent", "people", "human", "child" ], + char: "\ud83d\udc68\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_boy_boy: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66", + fitzpatrick_scale: false, + category: "people" + }, + family_man_girl_girl: { + keywords: [ "home", "parent", "people", "human", "children" ], + char: "\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67", + fitzpatrick_scale: false, + category: "people" + }, + yarn: { + keywords: [ "ball", "crochet", "knit" ], + char: "\ud83e\uddf6", + fitzpatrick_scale: false, + category: "people" + }, + thread: { + keywords: [ "needle", "sewing", "spool", "string" ], + char: "\ud83e\uddf5", + fitzpatrick_scale: false, + category: "people" + }, + coat: { + keywords: [ "jacket" ], + char: "\ud83e\udde5", + fitzpatrick_scale: false, + category: "people" + }, + labcoat: { + keywords: [ "doctor", "experiment", "scientist", "chemist" ], + char: "\ud83e\udd7c", + fitzpatrick_scale: false, + category: "people" + }, + womans_clothes: { + keywords: [ "fashion", "shopping_bags", "female" ], + char: "\ud83d\udc5a", + fitzpatrick_scale: false, + category: "people" + }, + tshirt: { + keywords: [ "fashion", "cloth", "casual", "shirt", "tee" ], + char: "\ud83d\udc55", + fitzpatrick_scale: false, + category: "people" + }, + jeans: { + keywords: [ "fashion", "shopping" ], + char: "\ud83d\udc56", + fitzpatrick_scale: false, + category: "people" + }, + necktie: { + keywords: [ "shirt", "suitup", "formal", "fashion", "cloth", "business" ], + char: "\ud83d\udc54", + fitzpatrick_scale: false, + category: "people" + }, + dress: { + keywords: [ "clothes", "fashion", "shopping" ], + char: "\ud83d\udc57", + fitzpatrick_scale: false, + category: "people" + }, + bikini: { + keywords: [ "swimming", "female", "woman", "girl", "fashion", "beach", "summer" ], + char: "\ud83d\udc59", + fitzpatrick_scale: false, + category: "people" + }, + kimono: { + keywords: [ "dress", "fashion", "women", "female", "japanese" ], + char: "\ud83d\udc58", + fitzpatrick_scale: false, + category: "people" + }, + lipstick: { + keywords: [ "female", "girl", "fashion", "woman" ], + char: "\ud83d\udc84", + fitzpatrick_scale: false, + category: "people" + }, + kiss: { + keywords: [ "face", "lips", "love", "like", "affection", "valentines" ], + char: "\ud83d\udc8b", + fitzpatrick_scale: false, + category: "people" + }, + footprints: { + keywords: [ "feet", "tracking", "walking", "beach" ], + char: "\ud83d\udc63", + fitzpatrick_scale: false, + category: "people" + }, + flat_shoe: { + keywords: [ "ballet", "slip-on", "slipper" ], + char: "\ud83e\udd7f", + fitzpatrick_scale: false, + category: "people" + }, + high_heel: { + keywords: [ "fashion", "shoes", "female", "pumps", "stiletto" ], + char: "\ud83d\udc60", + fitzpatrick_scale: false, + category: "people" + }, + sandal: { + keywords: [ "shoes", "fashion", "flip flops" ], + char: "\ud83d\udc61", + fitzpatrick_scale: false, + category: "people" + }, + boot: { + keywords: [ "shoes", "fashion" ], + char: "\ud83d\udc62", + fitzpatrick_scale: false, + category: "people" + }, + mans_shoe: { + keywords: [ "fashion", "male" ], + char: "\ud83d\udc5e", + fitzpatrick_scale: false, + category: "people" + }, + athletic_shoe: { + keywords: [ "shoes", "sports", "sneakers" ], + char: "\ud83d\udc5f", + fitzpatrick_scale: false, + category: "people" + }, + hiking_boot: { + keywords: [ "backpacking", "camping", "hiking" ], + char: "\ud83e\udd7e", + fitzpatrick_scale: false, + category: "people" + }, + socks: { + keywords: [ "stockings", "clothes" ], + char: "\ud83e\udde6", + fitzpatrick_scale: false, + category: "people" + }, + gloves: { + keywords: [ "hands", "winter", "clothes" ], + char: "\ud83e\udde4", + fitzpatrick_scale: false, + category: "people" + }, + scarf: { + keywords: [ "neck", "winter", "clothes" ], + char: "\ud83e\udde3", + fitzpatrick_scale: false, + category: "people" + }, + womans_hat: { + keywords: [ "fashion", "accessories", "female", "lady", "spring" ], + char: "\ud83d\udc52", + fitzpatrick_scale: false, + category: "people" + }, + tophat: { + keywords: [ "magic", "gentleman", "classy", "circus" ], + char: "\ud83c\udfa9", + fitzpatrick_scale: false, + category: "people" + }, + billed_hat: { + keywords: [ "cap", "baseball" ], + char: "\ud83e\udde2", + fitzpatrick_scale: false, + category: "people" + }, + rescue_worker_helmet: { + keywords: [ "construction", "build" ], + char: "\u26d1", + fitzpatrick_scale: false, + category: "people" + }, + mortar_board: { + keywords: [ "school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education" ], + char: "\ud83c\udf93", + fitzpatrick_scale: false, + category: "people" + }, + crown: { + keywords: [ "king", "kod", "leader", "royalty", "lord" ], + char: "\ud83d\udc51", + fitzpatrick_scale: false, + category: "people" + }, + school_satchel: { + keywords: [ "student", "education", "bag", "backpack" ], + char: "\ud83c\udf92", + fitzpatrick_scale: false, + category: "people" + }, + luggage: { + keywords: [ "packing", "travel" ], + char: "\ud83e\uddf3", + fitzpatrick_scale: false, + category: "people" + }, + pouch: { + keywords: [ "bag", "accessories", "shopping" ], + char: "\ud83d\udc5d", + fitzpatrick_scale: false, + category: "people" + }, + purse: { + keywords: [ "fashion", "accessories", "money", "sales", "shopping" ], + char: "\ud83d\udc5b", + fitzpatrick_scale: false, + category: "people" + }, + handbag: { + keywords: [ "fashion", "accessory", "accessories", "shopping" ], + char: "\ud83d\udc5c", + fitzpatrick_scale: false, + category: "people" + }, + briefcase: { + keywords: [ "business", "documents", "work", "law", "legal", "job", "career" ], + char: "\ud83d\udcbc", + fitzpatrick_scale: false, + category: "people" + }, + eyeglasses: { + keywords: [ "fashion", "accessories", "eyesight", "nerdy", "dork", "geek" ], + char: "\ud83d\udc53", + fitzpatrick_scale: false, + category: "people" + }, + dark_sunglasses: { + keywords: [ "face", "cool", "accessories" ], + char: "\ud83d\udd76", + fitzpatrick_scale: false, + category: "people" + }, + goggles: { + keywords: [ "eyes", "protection", "safety" ], + char: "\ud83e\udd7d", + fitzpatrick_scale: false, + category: "people" + }, + ring: { + keywords: [ "wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement" ], + char: "\ud83d\udc8d", + fitzpatrick_scale: false, + category: "people" + }, + closed_umbrella: { + keywords: [ "weather", "rain", "drizzle" ], + char: "\ud83c\udf02", + fitzpatrick_scale: false, + category: "people" + }, + dog: { + keywords: [ "animal", "friend", "nature", "woof", "puppy", "pet", "faithful" ], + char: "\ud83d\udc36", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cat: { + keywords: [ "animal", "meow", "nature", "pet", "kitten" ], + char: "\ud83d\udc31", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mouse: { + keywords: [ "animal", "nature", "cheese_wedge", "rodent" ], + char: "\ud83d\udc2d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hamster: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc39", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rabbit: { + keywords: [ "animal", "nature", "pet", "spring", "magic", "bunny" ], + char: "\ud83d\udc30", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fox_face: { + keywords: [ "animal", "nature", "face" ], + char: "\ud83e\udd8a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bear: { + keywords: [ "animal", "nature", "wild" ], + char: "\ud83d\udc3b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + panda_face: { + keywords: [ "animal", "nature", "panda" ], + char: "\ud83d\udc3c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + koala: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc28", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tiger: { + keywords: [ "animal", "cat", "danger", "wild", "nature", "roar" ], + char: "\ud83d\udc2f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lion: { + keywords: [ "animal", "nature" ], + char: "\ud83e\udd81", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cow: { + keywords: [ "beef", "ox", "animal", "nature", "moo", "milk" ], + char: "\ud83d\udc2e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig: { + keywords: [ "animal", "oink", "nature" ], + char: "\ud83d\udc37", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig_nose: { + keywords: [ "animal", "oink" ], + char: "\ud83d\udc3d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + frog: { + keywords: [ "animal", "nature", "croak", "toad" ], + char: "\ud83d\udc38", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + squid: { + keywords: [ "animal", "nature", "ocean", "sea" ], + char: "\ud83e\udd91", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + octopus: { + keywords: [ "animal", "creature", "ocean", "sea", "nature", "beach" ], + char: "\ud83d\udc19", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shrimp: { + keywords: [ "animal", "ocean", "nature", "seafood" ], + char: "\ud83e\udd90", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + monkey_face: { + keywords: [ "animal", "nature", "circus" ], + char: "\ud83d\udc35", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + gorilla: { + keywords: [ "animal", "nature", "circus" ], + char: "\ud83e\udd8d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + see_no_evil: { + keywords: [ "monkey", "animal", "nature", "haha" ], + char: "\ud83d\ude48", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hear_no_evil: { + keywords: [ "animal", "monkey", "nature" ], + char: "\ud83d\ude49", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + speak_no_evil: { + keywords: [ "monkey", "animal", "nature", "omg" ], + char: "\ud83d\ude4a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + monkey: { + keywords: [ "animal", "nature", "banana", "circus" ], + char: "\ud83d\udc12", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chicken: { + keywords: [ "animal", "cluck", "nature", "bird" ], + char: "\ud83d\udc14", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + penguin: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc27", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bird: { + keywords: [ "animal", "nature", "fly", "tweet", "spring" ], + char: "\ud83d\udc26", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + baby_chick: { + keywords: [ "animal", "chicken", "bird" ], + char: "\ud83d\udc24", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hatching_chick: { + keywords: [ "animal", "chicken", "egg", "born", "baby", "bird" ], + char: "\ud83d\udc23", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hatched_chick: { + keywords: [ "animal", "chicken", "baby", "bird" ], + char: "\ud83d\udc25", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + duck: { + keywords: [ "animal", "nature", "bird", "mallard" ], + char: "\ud83e\udd86", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + eagle: { + keywords: [ "animal", "nature", "bird" ], + char: "\ud83e\udd85", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + owl: { + keywords: [ "animal", "nature", "bird", "hoot" ], + char: "\ud83e\udd89", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bat: { + keywords: [ "animal", "nature", "blind", "vampire" ], + char: "\ud83e\udd87", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wolf: { + keywords: [ "animal", "nature", "wild" ], + char: "\ud83d\udc3a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + boar: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc17", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + horse: { + keywords: [ "animal", "brown", "nature" ], + char: "\ud83d\udc34", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + unicorn: { + keywords: [ "animal", "nature", "mystical" ], + char: "\ud83e\udd84", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + honeybee: { + keywords: [ "animal", "insect", "nature", "bug", "spring", "honey" ], + char: "\ud83d\udc1d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bug: { + keywords: [ "animal", "insect", "nature", "worm" ], + char: "\ud83d\udc1b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + butterfly: { + keywords: [ "animal", "insect", "nature", "caterpillar" ], + char: "\ud83e\udd8b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snail: { + keywords: [ "slow", "animal", "shell" ], + char: "\ud83d\udc0c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + beetle: { + keywords: [ "animal", "insect", "nature", "ladybug" ], + char: "\ud83d\udc1e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ant: { + keywords: [ "animal", "insect", "nature", "bug" ], + char: "\ud83d\udc1c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + grasshopper: { + keywords: [ "animal", "cricket", "chirp" ], + char: "\ud83e\udd97", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + spider: { + keywords: [ "animal", "arachnid" ], + char: "\ud83d\udd77", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + scorpion: { + keywords: [ "animal", "arachnid" ], + char: "\ud83e\udd82", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crab: { + keywords: [ "animal", "crustacean" ], + char: "\ud83e\udd80", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snake: { + keywords: [ "animal", "evil", "nature", "hiss", "python" ], + char: "\ud83d\udc0d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lizard: { + keywords: [ "animal", "nature", "reptile" ], + char: "\ud83e\udd8e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + "t-rex": { + keywords: [ "animal", "nature", "dinosaur", "tyrannosaurus", "extinct" ], + char: "\ud83e\udd96", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sauropod: { + keywords: [ "animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct" ], + char: "\ud83e\udd95", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + turtle: { + keywords: [ "animal", "slow", "nature", "tortoise" ], + char: "\ud83d\udc22", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tropical_fish: { + keywords: [ "animal", "swim", "ocean", "beach", "nemo" ], + char: "\ud83d\udc20", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fish: { + keywords: [ "animal", "food", "nature" ], + char: "\ud83d\udc1f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + blowfish: { + keywords: [ "animal", "nature", "food", "sea", "ocean" ], + char: "\ud83d\udc21", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dolphin: { + keywords: [ "animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach" ], + char: "\ud83d\udc2c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shark: { + keywords: [ "animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach" ], + char: "\ud83e\udd88", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + whale: { + keywords: [ "animal", "nature", "sea", "ocean" ], + char: "\ud83d\udc33", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + whale2: { + keywords: [ "animal", "nature", "sea", "ocean" ], + char: "\ud83d\udc0b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crocodile: { + keywords: [ "animal", "nature", "reptile", "lizard", "alligator" ], + char: "\ud83d\udc0a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + leopard: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc06", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + zebra: { + keywords: [ "animal", "nature", "stripes", "safari" ], + char: "\ud83e\udd93", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tiger2: { + keywords: [ "animal", "nature", "roar" ], + char: "\ud83d\udc05", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + water_buffalo: { + keywords: [ "animal", "nature", "ox", "cow" ], + char: "\ud83d\udc03", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ox: { + keywords: [ "animal", "cow", "beef" ], + char: "\ud83d\udc02", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cow2: { + keywords: [ "beef", "ox", "animal", "nature", "moo", "milk" ], + char: "\ud83d\udc04", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + deer: { + keywords: [ "animal", "nature", "horns", "venison" ], + char: "\ud83e\udd8c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dromedary_camel: { + keywords: [ "animal", "hot", "desert", "hump" ], + char: "\ud83d\udc2a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + camel: { + keywords: [ "animal", "nature", "hot", "desert", "hump" ], + char: "\ud83d\udc2b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + giraffe: { + keywords: [ "animal", "nature", "spots", "safari" ], + char: "\ud83e\udd92", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + elephant: { + keywords: [ "animal", "nature", "nose", "th", "circus" ], + char: "\ud83d\udc18", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rhinoceros: { + keywords: [ "animal", "nature", "horn" ], + char: "\ud83e\udd8f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + goat: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc10", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ram: { + keywords: [ "animal", "sheep", "nature" ], + char: "\ud83d\udc0f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sheep: { + keywords: [ "animal", "nature", "wool", "shipit" ], + char: "\ud83d\udc11", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + racehorse: { + keywords: [ "animal", "gamble", "luck" ], + char: "\ud83d\udc0e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + pig2: { + keywords: [ "animal", "nature" ], + char: "\ud83d\udc16", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rat: { + keywords: [ "animal", "mouse", "rodent" ], + char: "\ud83d\udc00", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mouse2: { + keywords: [ "animal", "nature", "rodent" ], + char: "\ud83d\udc01", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rooster: { + keywords: [ "animal", "nature", "chicken" ], + char: "\ud83d\udc13", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + turkey: { + keywords: [ "animal", "bird" ], + char: "\ud83e\udd83", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dove: { + keywords: [ "animal", "bird" ], + char: "\ud83d\udd4a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dog2: { + keywords: [ "animal", "nature", "friend", "doge", "pet", "faithful" ], + char: "\ud83d\udc15", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + poodle: { + keywords: [ "dog", "animal", "101", "nature", "pet" ], + char: "\ud83d\udc29", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cat2: { + keywords: [ "animal", "meow", "pet", "cats" ], + char: "\ud83d\udc08", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rabbit2: { + keywords: [ "animal", "nature", "pet", "magic", "spring" ], + char: "\ud83d\udc07", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chipmunk: { + keywords: [ "animal", "nature", "rodent", "squirrel" ], + char: "\ud83d\udc3f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hedgehog: { + keywords: [ "animal", "nature", "spiny" ], + char: "\ud83e\udd94", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + raccoon: { + keywords: [ "animal", "nature" ], + char: "\ud83e\udd9d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + llama: { + keywords: [ "animal", "nature", "alpaca" ], + char: "\ud83e\udd99", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hippopotamus: { + keywords: [ "animal", "nature" ], + char: "\ud83e\udd9b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + kangaroo: { + keywords: [ "animal", "nature", "australia", "joey", "hop", "marsupial" ], + char: "\ud83e\udd98", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + badger: { + keywords: [ "animal", "nature", "honey" ], + char: "\ud83e\udda1", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + swan: { + keywords: [ "animal", "nature", "bird" ], + char: "\ud83e\udda2", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + peacock: { + keywords: [ "animal", "nature", "peahen", "bird" ], + char: "\ud83e\udd9a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + parrot: { + keywords: [ "animal", "nature", "bird", "pirate", "talk" ], + char: "\ud83e\udd9c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + lobster: { + keywords: [ "animal", "nature", "bisque", "claws", "seafood" ], + char: "\ud83e\udd9e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mosquito: { + keywords: [ "animal", "nature", "insect", "malaria" ], + char: "\ud83e\udd9f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + paw_prints: { + keywords: [ "animal", "tracking", "footprints", "dog", "cat", "pet", "feet" ], + char: "\ud83d\udc3e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dragon: { + keywords: [ "animal", "myth", "nature", "chinese", "green" ], + char: "\ud83d\udc09", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dragon_face: { + keywords: [ "animal", "myth", "nature", "chinese", "green" ], + char: "\ud83d\udc32", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cactus: { + keywords: [ "vegetable", "plant", "nature" ], + char: "\ud83c\udf35", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + christmas_tree: { + keywords: [ "festival", "vacation", "december", "xmas", "celebration" ], + char: "\ud83c\udf84", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + evergreen_tree: { + keywords: [ "plant", "nature" ], + char: "\ud83c\udf32", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + deciduous_tree: { + keywords: [ "plant", "nature" ], + char: "\ud83c\udf33", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + palm_tree: { + keywords: [ "plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical" ], + char: "\ud83c\udf34", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + seedling: { + keywords: [ "plant", "nature", "grass", "lawn", "spring" ], + char: "\ud83c\udf31", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + herb: { + keywords: [ "vegetable", "plant", "medicine", "weed", "grass", "lawn" ], + char: "\ud83c\udf3f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shamrock: { + keywords: [ "vegetable", "plant", "nature", "irish", "clover" ], + char: "\u2618", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + four_leaf_clover: { + keywords: [ "vegetable", "plant", "nature", "lucky", "irish" ], + char: "\ud83c\udf40", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bamboo: { + keywords: [ "plant", "nature", "vegetable", "panda", "pine_decoration" ], + char: "\ud83c\udf8d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tanabata_tree: { + keywords: [ "plant", "nature", "branch", "summer" ], + char: "\ud83c\udf8b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + leaves: { + keywords: [ "nature", "plant", "tree", "vegetable", "grass", "lawn", "spring" ], + char: "\ud83c\udf43", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fallen_leaf: { + keywords: [ "nature", "plant", "vegetable", "leaves" ], + char: "\ud83c\udf42", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + maple_leaf: { + keywords: [ "nature", "plant", "vegetable", "ca", "fall" ], + char: "\ud83c\udf41", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ear_of_rice: { + keywords: [ "nature", "plant" ], + char: "\ud83c\udf3e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + hibiscus: { + keywords: [ "plant", "vegetable", "flowers", "beach" ], + char: "\ud83c\udf3a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sunflower: { + keywords: [ "nature", "plant", "fall" ], + char: "\ud83c\udf3b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + rose: { + keywords: [ "flowers", "valentines", "love", "spring" ], + char: "\ud83c\udf39", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wilted_flower: { + keywords: [ "plant", "nature", "flower" ], + char: "\ud83e\udd40", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tulip: { + keywords: [ "flowers", "plant", "nature", "summer", "spring" ], + char: "\ud83c\udf37", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + blossom: { + keywords: [ "nature", "flowers", "yellow" ], + char: "\ud83c\udf3c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cherry_blossom: { + keywords: [ "nature", "plant", "spring", "flower" ], + char: "\ud83c\udf38", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + bouquet: { + keywords: [ "flowers", "nature", "spring" ], + char: "\ud83d\udc90", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + mushroom: { + keywords: [ "plant", "vegetable" ], + char: "\ud83c\udf44", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + chestnut: { + keywords: [ "food", "squirrel" ], + char: "\ud83c\udf30", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + jack_o_lantern: { + keywords: [ "halloween", "light", "pumpkin", "creepy", "fall" ], + char: "\ud83c\udf83", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + shell: { + keywords: [ "nature", "sea", "beach" ], + char: "\ud83d\udc1a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + spider_web: { + keywords: [ "animal", "insect", "arachnid", "silk" ], + char: "\ud83d\udd78", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_americas: { + keywords: [ "globe", "world", "USA", "international" ], + char: "\ud83c\udf0e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_africa: { + keywords: [ "globe", "world", "international" ], + char: "\ud83c\udf0d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + earth_asia: { + keywords: [ "globe", "world", "east", "international" ], + char: "\ud83c\udf0f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + full_moon: { + keywords: [ "nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf15", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waning_gibbous_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon" ], + char: "\ud83c\udf16", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + last_quarter_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf17", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waning_crescent_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf18", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + new_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf11", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waxing_crescent_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf12", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + first_quarter_moon: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf13", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + waxing_gibbous_moon: { + keywords: [ "nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep" ], + char: "\ud83c\udf14", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + new_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf1a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + full_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf1d", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + first_quarter_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf1b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + last_quarter_moon_with_face: { + keywords: [ "nature", "twilight", "planet", "space", "night", "evening", "sleep" ], + char: "\ud83c\udf1c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_with_face: { + keywords: [ "nature", "morning", "sky" ], + char: "\ud83c\udf1e", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + crescent_moon: { + keywords: [ "night", "sleep", "sky", "evening", "magic" ], + char: "\ud83c\udf19", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + star: { + keywords: [ "night", "yellow" ], + char: "\u2b50", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + star2: { + keywords: [ "night", "sparkle", "awesome", "good", "magic" ], + char: "\ud83c\udf1f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dizzy: { + keywords: [ "star", "sparkle", "shoot", "magic" ], + char: "\ud83d\udcab", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sparkles: { + keywords: [ "stars", "shine", "shiny", "cool", "awesome", "good", "magic" ], + char: "\u2728", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + comet: { + keywords: [ "space" ], + char: "\u2604", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sunny: { + keywords: [ "weather", "nature", "brightness", "summer", "beach", "spring" ], + char: "\u2600\ufe0f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_small_cloud: { + keywords: [ "weather" ], + char: "\ud83c\udf24", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + partly_sunny: { + keywords: [ "weather", "nature", "cloudy", "morning", "fall", "spring" ], + char: "\u26c5", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_large_cloud: { + keywords: [ "weather" ], + char: "\ud83c\udf25", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sun_behind_rain_cloud: { + keywords: [ "weather" ], + char: "\ud83c\udf26", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud: { + keywords: [ "weather", "sky" ], + char: "\u2601\ufe0f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_rain: { + keywords: [ "weather" ], + char: "\ud83c\udf27", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_lightning_and_rain: { + keywords: [ "weather", "lightning" ], + char: "\u26c8", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_lightning: { + keywords: [ "weather", "thunder" ], + char: "\ud83c\udf29", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + zap: { + keywords: [ "thunder", "weather", "lightning bolt", "fast" ], + char: "\u26a1", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fire: { + keywords: [ "hot", "cook", "flame" ], + char: "\ud83d\udd25", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + boom: { + keywords: [ "bomb", "explode", "explosion", "collision", "blown" ], + char: "\ud83d\udca5", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowflake: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas" ], + char: "\u2744\ufe0f", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + cloud_with_snow: { + keywords: [ "weather" ], + char: "\ud83c\udf28", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowman: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow" ], + char: "\u26c4", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + snowman_with_snow: { + keywords: [ "winter", "season", "cold", "weather", "christmas", "xmas", "frozen" ], + char: "\u2603", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + wind_face: { + keywords: [ "gust", "air" ], + char: "\ud83c\udf2c", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + dash: { + keywords: [ "wind", "air", "fast", "shoo", "fart", "smoke", "puff" ], + char: "\ud83d\udca8", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + tornado: { + keywords: [ "weather", "cyclone", "twister" ], + char: "\ud83c\udf2a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + fog: { + keywords: [ "weather" ], + char: "\ud83c\udf2b", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + open_umbrella: { + keywords: [ "weather", "spring" ], + char: "\u2602", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + umbrella: { + keywords: [ "rainy", "weather", "spring" ], + char: "\u2614", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + droplet: { + keywords: [ "water", "drip", "faucet", "spring" ], + char: "\ud83d\udca7", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + sweat_drops: { + keywords: [ "water", "drip", "oops" ], + char: "\ud83d\udca6", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + ocean: { + keywords: [ "sea", "water", "wave", "nature", "tsunami", "disaster" ], + char: "\ud83c\udf0a", + fitzpatrick_scale: false, + category: "animals_and_nature" + }, + green_apple: { + keywords: [ "fruit", "nature" ], + char: "\ud83c\udf4f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + apple: { + keywords: [ "fruit", "mac", "school" ], + char: "\ud83c\udf4e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pear: { + keywords: [ "fruit", "nature", "food" ], + char: "\ud83c\udf50", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tangerine: { + keywords: [ "food", "fruit", "nature", "orange" ], + char: "\ud83c\udf4a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + lemon: { + keywords: [ "fruit", "nature" ], + char: "\ud83c\udf4b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + banana: { + keywords: [ "fruit", "food", "monkey" ], + char: "\ud83c\udf4c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + watermelon: { + keywords: [ "fruit", "food", "picnic", "summer" ], + char: "\ud83c\udf49", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + grapes: { + keywords: [ "fruit", "food", "wine" ], + char: "\ud83c\udf47", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + strawberry: { + keywords: [ "fruit", "food", "nature" ], + char: "\ud83c\udf53", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + melon: { + keywords: [ "fruit", "nature", "food" ], + char: "\ud83c\udf48", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cherries: { + keywords: [ "food", "fruit" ], + char: "\ud83c\udf52", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + peach: { + keywords: [ "fruit", "nature", "food" ], + char: "\ud83c\udf51", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pineapple: { + keywords: [ "fruit", "nature", "food" ], + char: "\ud83c\udf4d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + coconut: { + keywords: [ "fruit", "nature", "food", "palm" ], + char: "\ud83e\udd65", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + kiwi_fruit: { + keywords: [ "fruit", "food" ], + char: "\ud83e\udd5d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + mango: { + keywords: [ "fruit", "food", "tropical" ], + char: "\ud83e\udd6d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + avocado: { + keywords: [ "fruit", "food" ], + char: "\ud83e\udd51", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + broccoli: { + keywords: [ "fruit", "food", "vegetable" ], + char: "\ud83e\udd66", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tomato: { + keywords: [ "fruit", "vegetable", "nature", "food" ], + char: "\ud83c\udf45", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + eggplant: { + keywords: [ "vegetable", "nature", "food", "aubergine" ], + char: "\ud83c\udf46", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cucumber: { + keywords: [ "fruit", "food", "pickle" ], + char: "\ud83e\udd52", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + carrot: { + keywords: [ "vegetable", "food", "orange" ], + char: "\ud83e\udd55", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hot_pepper: { + keywords: [ "food", "spicy", "chilli", "chili" ], + char: "\ud83c\udf36", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + potato: { + keywords: [ "food", "tuber", "vegatable", "starch" ], + char: "\ud83e\udd54", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + corn: { + keywords: [ "food", "vegetable", "plant" ], + char: "\ud83c\udf3d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + leafy_greens: { + keywords: [ "food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce" ], + char: "\ud83e\udd6c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sweet_potato: { + keywords: [ "food", "nature" ], + char: "\ud83c\udf60", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + peanuts: { + keywords: [ "food", "nut" ], + char: "\ud83e\udd5c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + honey_pot: { + keywords: [ "bees", "sweet", "kitchen" ], + char: "\ud83c\udf6f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + croissant: { + keywords: [ "food", "bread", "french" ], + char: "\ud83e\udd50", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bread: { + keywords: [ "food", "wheat", "breakfast", "toast" ], + char: "\ud83c\udf5e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + baguette_bread: { + keywords: [ "food", "bread", "french" ], + char: "\ud83e\udd56", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bagel: { + keywords: [ "food", "bread", "bakery", "schmear" ], + char: "\ud83e\udd6f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pretzel: { + keywords: [ "food", "bread", "twisted" ], + char: "\ud83e\udd68", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cheese: { + keywords: [ "food", "chadder" ], + char: "\ud83e\uddc0", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + egg: { + keywords: [ "food", "chicken", "breakfast" ], + char: "\ud83e\udd5a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bacon: { + keywords: [ "food", "breakfast", "pork", "pig", "meat" ], + char: "\ud83e\udd53", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + steak: { + keywords: [ "food", "cow", "meat", "cut", "chop", "lambchop", "porkchop" ], + char: "\ud83e\udd69", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pancakes: { + keywords: [ "food", "breakfast", "flapjacks", "hotcakes" ], + char: "\ud83e\udd5e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + poultry_leg: { + keywords: [ "food", "meat", "drumstick", "bird", "chicken", "turkey" ], + char: "\ud83c\udf57", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + meat_on_bone: { + keywords: [ "good", "food", "drumstick" ], + char: "\ud83c\udf56", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bone: { + keywords: [ "skeleton" ], + char: "\ud83e\uddb4", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fried_shrimp: { + keywords: [ "food", "animal", "appetizer", "summer" ], + char: "\ud83c\udf64", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fried_egg: { + keywords: [ "food", "breakfast", "kitchen", "egg" ], + char: "\ud83c\udf73", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hamburger: { + keywords: [ "meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king" ], + char: "\ud83c\udf54", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fries: { + keywords: [ "chips", "snack", "fast food" ], + char: "\ud83c\udf5f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + stuffed_flatbread: { + keywords: [ "food", "flatbread", "stuffed", "gyro" ], + char: "\ud83e\udd59", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + hotdog: { + keywords: [ "food", "frankfurter" ], + char: "\ud83c\udf2d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pizza: { + keywords: [ "food", "party" ], + char: "\ud83c\udf55", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sandwich: { + keywords: [ "food", "lunch", "bread" ], + char: "\ud83e\udd6a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + canned_food: { + keywords: [ "food", "soup" ], + char: "\ud83e\udd6b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + spaghetti: { + keywords: [ "food", "italian", "noodle" ], + char: "\ud83c\udf5d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + taco: { + keywords: [ "food", "mexican" ], + char: "\ud83c\udf2e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + burrito: { + keywords: [ "food", "mexican" ], + char: "\ud83c\udf2f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + green_salad: { + keywords: [ "food", "healthy", "lettuce" ], + char: "\ud83e\udd57", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + shallow_pan_of_food: { + keywords: [ "food", "cooking", "casserole", "paella" ], + char: "\ud83e\udd58", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + ramen: { + keywords: [ "food", "japanese", "noodle", "chopsticks" ], + char: "\ud83c\udf5c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + stew: { + keywords: [ "food", "meat", "soup" ], + char: "\ud83c\udf72", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fish_cake: { + keywords: [ "food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen" ], + char: "\ud83c\udf65", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fortune_cookie: { + keywords: [ "food", "prophecy" ], + char: "\ud83e\udd60", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sushi: { + keywords: [ "food", "fish", "japanese", "rice" ], + char: "\ud83c\udf63", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bento: { + keywords: [ "food", "japanese", "box" ], + char: "\ud83c\udf71", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + curry: { + keywords: [ "food", "spicy", "hot", "indian" ], + char: "\ud83c\udf5b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice_ball: { + keywords: [ "food", "japanese" ], + char: "\ud83c\udf59", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice: { + keywords: [ "food", "china", "asian" ], + char: "\ud83c\udf5a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + rice_cracker: { + keywords: [ "food", "japanese" ], + char: "\ud83c\udf58", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + oden: { + keywords: [ "food", "japanese" ], + char: "\ud83c\udf62", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + dango: { + keywords: [ "food", "dessert", "sweet", "japanese", "barbecue", "meat" ], + char: "\ud83c\udf61", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + shaved_ice: { + keywords: [ "hot", "dessert", "summer" ], + char: "\ud83c\udf67", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + ice_cream: { + keywords: [ "food", "hot", "dessert" ], + char: "\ud83c\udf68", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + icecream: { + keywords: [ "food", "hot", "dessert", "summer" ], + char: "\ud83c\udf66", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + pie: { + keywords: [ "food", "dessert", "pastry" ], + char: "\ud83e\udd67", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cake: { + keywords: [ "food", "dessert" ], + char: "\ud83c\udf70", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cupcake: { + keywords: [ "food", "dessert", "bakery", "sweet" ], + char: "\ud83e\uddc1", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + moon_cake: { + keywords: [ "food", "autumn" ], + char: "\ud83e\udd6e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + birthday: { + keywords: [ "food", "dessert", "cake" ], + char: "\ud83c\udf82", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + custard: { + keywords: [ "dessert", "food" ], + char: "\ud83c\udf6e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + candy: { + keywords: [ "snack", "dessert", "sweet", "lolly" ], + char: "\ud83c\udf6c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + lollipop: { + keywords: [ "food", "snack", "candy", "sweet" ], + char: "\ud83c\udf6d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + chocolate_bar: { + keywords: [ "food", "snack", "dessert", "sweet" ], + char: "\ud83c\udf6b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + popcorn: { + keywords: [ "food", "movie theater", "films", "snack" ], + char: "\ud83c\udf7f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + dumpling: { + keywords: [ "food", "empanada", "pierogi", "potsticker" ], + char: "\ud83e\udd5f", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + doughnut: { + keywords: [ "food", "dessert", "snack", "sweet", "donut" ], + char: "\ud83c\udf69", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cookie: { + keywords: [ "food", "snack", "oreo", "chocolate", "sweet", "dessert" ], + char: "\ud83c\udf6a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + milk_glass: { + keywords: [ "beverage", "drink", "cow" ], + char: "\ud83e\udd5b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + beer: { + keywords: [ "relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze" ], + char: "\ud83c\udf7a", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + beers: { + keywords: [ "relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze" ], + char: "\ud83c\udf7b", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + clinking_glasses: { + keywords: [ "beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast" ], + char: "\ud83e\udd42", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + wine_glass: { + keywords: [ "drink", "beverage", "drunk", "alcohol", "booze" ], + char: "\ud83c\udf77", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tumbler_glass: { + keywords: [ "drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot" ], + char: "\ud83e\udd43", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cocktail: { + keywords: [ "drink", "drunk", "alcohol", "beverage", "booze", "mojito" ], + char: "\ud83c\udf78", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tropical_drink: { + keywords: [ "beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito" ], + char: "\ud83c\udf79", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + champagne: { + keywords: [ "drink", "wine", "bottle", "celebration" ], + char: "\ud83c\udf7e", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + sake: { + keywords: [ "wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze" ], + char: "\ud83c\udf76", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + tea: { + keywords: [ "drink", "bowl", "breakfast", "green", "british" ], + char: "\ud83c\udf75", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + cup_with_straw: { + keywords: [ "drink", "soda" ], + char: "\ud83e\udd64", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + coffee: { + keywords: [ "beverage", "caffeine", "latte", "espresso" ], + char: "\u2615", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + baby_bottle: { + keywords: [ "food", "container", "milk" ], + char: "\ud83c\udf7c", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + salt: { + keywords: [ "condiment", "shaker" ], + char: "\ud83e\uddc2", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + spoon: { + keywords: [ "cutlery", "kitchen", "tableware" ], + char: "\ud83e\udd44", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + fork_and_knife: { + keywords: [ "cutlery", "kitchen" ], + char: "\ud83c\udf74", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + plate_with_cutlery: { + keywords: [ "food", "eat", "meal", "lunch", "dinner", "restaurant" ], + char: "\ud83c\udf7d", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + bowl_with_spoon: { + keywords: [ "food", "breakfast", "cereal", "oatmeal", "porridge" ], + char: "\ud83e\udd63", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + takeout_box: { + keywords: [ "food", "leftovers" ], + char: "\ud83e\udd61", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + chopsticks: { + keywords: [ "food" ], + char: "\ud83e\udd62", + fitzpatrick_scale: false, + category: "food_and_drink" + }, + soccer: { + keywords: [ "sports", "football" ], + char: "\u26bd", + fitzpatrick_scale: false, + category: "activity" + }, + basketball: { + keywords: [ "sports", "balls", "NBA" ], + char: "\ud83c\udfc0", + fitzpatrick_scale: false, + category: "activity" + }, + football: { + keywords: [ "sports", "balls", "NFL" ], + char: "\ud83c\udfc8", + fitzpatrick_scale: false, + category: "activity" + }, + baseball: { + keywords: [ "sports", "balls" ], + char: "\u26be", + fitzpatrick_scale: false, + category: "activity" + }, + softball: { + keywords: [ "sports", "balls" ], + char: "\ud83e\udd4e", + fitzpatrick_scale: false, + category: "activity" + }, + tennis: { + keywords: [ "sports", "balls", "green" ], + char: "\ud83c\udfbe", + fitzpatrick_scale: false, + category: "activity" + }, + volleyball: { + keywords: [ "sports", "balls" ], + char: "\ud83c\udfd0", + fitzpatrick_scale: false, + category: "activity" + }, + rugby_football: { + keywords: [ "sports", "team" ], + char: "\ud83c\udfc9", + fitzpatrick_scale: false, + category: "activity" + }, + flying_disc: { + keywords: [ "sports", "frisbee", "ultimate" ], + char: "\ud83e\udd4f", + fitzpatrick_scale: false, + category: "activity" + }, + "8ball": { + keywords: [ "pool", "hobby", "game", "luck", "magic" ], + char: "\ud83c\udfb1", + fitzpatrick_scale: false, + category: "activity" + }, + golf: { + keywords: [ "sports", "business", "flag", "hole", "summer" ], + char: "\u26f3", + fitzpatrick_scale: false, + category: "activity" + }, + golfing_woman: { + keywords: [ "sports", "business", "woman", "female" ], + char: "\ud83c\udfcc\ufe0f\u200d\u2640\ufe0f", + fitzpatrick_scale: false, + category: "activity" + }, + golfing_man: { + keywords: [ "sports", "business" ], + char: "\ud83c\udfcc", + fitzpatrick_scale: true, + category: "activity" + }, + ping_pong: { + keywords: [ "sports", "pingpong" ], + char: "\ud83c\udfd3", + fitzpatrick_scale: false, + category: "activity" + }, + badminton: { + keywords: [ "sports" ], + char: "\ud83c\udff8", + fitzpatrick_scale: false, + category: "activity" + }, + goal_net: { + keywords: [ "sports" ], + char: "\ud83e\udd45", + fitzpatrick_scale: false, + category: "activity" + }, + ice_hockey: { + keywords: [ "sports" ], + char: "\ud83c\udfd2", + fitzpatrick_scale: false, + category: "activity" + }, + field_hockey: { + keywords: [ "sports" ], + char: "\ud83c\udfd1", + fitzpatrick_scale: false, + category: "activity" + }, + lacrosse: { + keywords: [ "sports", "ball", "stick" ], + char: "\ud83e\udd4d", + fitzpatrick_scale: false, + category: "activity" + }, + cricket: { + keywords: [ "sports" ], + char: "\ud83c\udfcf", + fitzpatrick_scale: false, + category: "activity" + }, + ski: { + keywords: [ "sports", "winter", "cold", "snow" ], + char: "\ud83c\udfbf", + fitzpatrick_scale: false, + category: "activity" + }, + skier: { + keywords: [ "sports", "winter", "snow" ], + char: "\u26f7", + fitzpatrick_scale: false, + category: "activity" + }, + snowboarder: { + keywords: [ "sports", "winter" ], + char: "\ud83c\udfc2", + fitzpatrick_scale: true, + category: "activity" + }, + person_fencing: { + keywords: [ "sports", "fencing", "sword" ], + char: "\ud83e\udd3a", + fitzpatrick_scale: false, + category: "activity" + }, + women_wrestling: { + keywords: [ "sports", "wrestlers" ], + char: "\ud83e\udd3c\u200d\u2640\ufe0f", + fitzpatrick_scale: false, + category: "activity" + }, + men_wrestling: { + keywords: [ "sports", "wrestlers" ], + char: "\ud83e\udd3c\u200d\u2642\ufe0f", + fitzpatrick_scale: false, + category: "activity" + }, + woman_cartwheeling: { + keywords: [ "gymnastics" ], + char: "\ud83e\udd38\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + man_cartwheeling: { + keywords: [ "gymnastics" ], + char: "\ud83e\udd38\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + woman_playing_handball: { + keywords: [ "sports" ], + char: "\ud83e\udd3e\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + man_playing_handball: { + keywords: [ "sports" ], + char: "\ud83e\udd3e\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + ice_skate: { + keywords: [ "sports" ], + char: "\u26f8", + fitzpatrick_scale: false, + category: "activity" + }, + curling_stone: { + keywords: [ "sports" ], + char: "\ud83e\udd4c", + fitzpatrick_scale: false, + category: "activity" + }, + skateboard: { + keywords: [ "board" ], + char: "\ud83d\udef9", + fitzpatrick_scale: false, + category: "activity" + }, + sled: { + keywords: [ "sleigh", "luge", "toboggan" ], + char: "\ud83d\udef7", + fitzpatrick_scale: false, + category: "activity" + }, + bow_and_arrow: { + keywords: [ "sports" ], + char: "\ud83c\udff9", + fitzpatrick_scale: false, + category: "activity" + }, + fishing_pole_and_fish: { + keywords: [ "food", "hobby", "summer" ], + char: "\ud83c\udfa3", + fitzpatrick_scale: false, + category: "activity" + }, + boxing_glove: { + keywords: [ "sports", "fighting" ], + char: "\ud83e\udd4a", + fitzpatrick_scale: false, + category: "activity" + }, + martial_arts_uniform: { + keywords: [ "judo", "karate", "taekwondo" ], + char: "\ud83e\udd4b", + fitzpatrick_scale: false, + category: "activity" + }, + rowing_woman: { + keywords: [ "sports", "hobby", "water", "ship", "woman", "female" ], + char: "\ud83d\udea3\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + rowing_man: { + keywords: [ "sports", "hobby", "water", "ship" ], + char: "\ud83d\udea3", + fitzpatrick_scale: true, + category: "activity" + }, + climbing_woman: { + keywords: [ "sports", "hobby", "woman", "female", "rock" ], + char: "\ud83e\uddd7\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + climbing_man: { + keywords: [ "sports", "hobby", "man", "male", "rock" ], + char: "\ud83e\uddd7\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + swimming_woman: { + keywords: [ "sports", "exercise", "human", "athlete", "water", "summer", "woman", "female" ], + char: "\ud83c\udfca\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + swimming_man: { + keywords: [ "sports", "exercise", "human", "athlete", "water", "summer" ], + char: "\ud83c\udfca", + fitzpatrick_scale: true, + category: "activity" + }, + woman_playing_water_polo: { + keywords: [ "sports", "pool" ], + char: "\ud83e\udd3d\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + man_playing_water_polo: { + keywords: [ "sports", "pool" ], + char: "\ud83e\udd3d\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + woman_in_lotus_position: { + keywords: [ "woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness" ], + char: "\ud83e\uddd8\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + man_in_lotus_position: { + keywords: [ "man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness" ], + char: "\ud83e\uddd8\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + surfing_woman: { + keywords: [ "sports", "ocean", "sea", "summer", "beach", "woman", "female" ], + char: "\ud83c\udfc4\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + surfing_man: { + keywords: [ "sports", "ocean", "sea", "summer", "beach" ], + char: "\ud83c\udfc4", + fitzpatrick_scale: true, + category: "activity" + }, + bath: { + keywords: [ "clean", "shower", "bathroom" ], + char: "\ud83d\udec0", + fitzpatrick_scale: true, + category: "activity" + }, + basketball_woman: { + keywords: [ "sports", "human", "woman", "female" ], + char: "\u26f9\ufe0f\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + basketball_man: { + keywords: [ "sports", "human" ], + char: "\u26f9", + fitzpatrick_scale: true, + category: "activity" + }, + weight_lifting_woman: { + keywords: [ "sports", "training", "exercise", "woman", "female" ], + char: "\ud83c\udfcb\ufe0f\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + weight_lifting_man: { + keywords: [ "sports", "training", "exercise" ], + char: "\ud83c\udfcb", + fitzpatrick_scale: true, + category: "activity" + }, + biking_woman: { + keywords: [ "sports", "bike", "exercise", "hipster", "woman", "female" ], + char: "\ud83d\udeb4\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + biking_man: { + keywords: [ "sports", "bike", "exercise", "hipster" ], + char: "\ud83d\udeb4", + fitzpatrick_scale: true, + category: "activity" + }, + mountain_biking_woman: { + keywords: [ "transportation", "sports", "human", "race", "bike", "woman", "female" ], + char: "\ud83d\udeb5\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + mountain_biking_man: { + keywords: [ "transportation", "sports", "human", "race", "bike" ], + char: "\ud83d\udeb5", + fitzpatrick_scale: true, + category: "activity" + }, + horse_racing: { + keywords: [ "animal", "betting", "competition", "gambling", "luck" ], + char: "\ud83c\udfc7", + fitzpatrick_scale: true, + category: "activity" + }, + business_suit_levitating: { + keywords: [ "suit", "business", "levitate", "hover", "jump" ], + char: "\ud83d\udd74", + fitzpatrick_scale: true, + category: "activity" + }, + trophy: { + keywords: [ "win", "award", "contest", "place", "ftw", "ceremony" ], + char: "\ud83c\udfc6", + fitzpatrick_scale: false, + category: "activity" + }, + running_shirt_with_sash: { + keywords: [ "play", "pageant" ], + char: "\ud83c\udfbd", + fitzpatrick_scale: false, + category: "activity" + }, + medal_sports: { + keywords: [ "award", "winning" ], + char: "\ud83c\udfc5", + fitzpatrick_scale: false, + category: "activity" + }, + medal_military: { + keywords: [ "award", "winning", "army" ], + char: "\ud83c\udf96", + fitzpatrick_scale: false, + category: "activity" + }, + "1st_place_medal": { + keywords: [ "award", "winning", "first" ], + char: "\ud83e\udd47", + fitzpatrick_scale: false, + category: "activity" + }, + "2nd_place_medal": { + keywords: [ "award", "second" ], + char: "\ud83e\udd48", + fitzpatrick_scale: false, + category: "activity" + }, + "3rd_place_medal": { + keywords: [ "award", "third" ], + char: "\ud83e\udd49", + fitzpatrick_scale: false, + category: "activity" + }, + reminder_ribbon: { + keywords: [ "sports", "cause", "support", "awareness" ], + char: "\ud83c\udf97", + fitzpatrick_scale: false, + category: "activity" + }, + rosette: { + keywords: [ "flower", "decoration", "military" ], + char: "\ud83c\udff5", + fitzpatrick_scale: false, + category: "activity" + }, + ticket: { + keywords: [ "event", "concert", "pass" ], + char: "\ud83c\udfab", + fitzpatrick_scale: false, + category: "activity" + }, + tickets: { + keywords: [ "sports", "concert", "entrance" ], + char: "\ud83c\udf9f", + fitzpatrick_scale: false, + category: "activity" + }, + performing_arts: { + keywords: [ "acting", "theater", "drama" ], + char: "\ud83c\udfad", + fitzpatrick_scale: false, + category: "activity" + }, + art: { + keywords: [ "design", "paint", "draw", "colors" ], + char: "\ud83c\udfa8", + fitzpatrick_scale: false, + category: "activity" + }, + circus_tent: { + keywords: [ "festival", "carnival", "party" ], + char: "\ud83c\udfaa", + fitzpatrick_scale: false, + category: "activity" + }, + woman_juggling: { + keywords: [ "juggle", "balance", "skill", "multitask" ], + char: "\ud83e\udd39\u200d\u2640\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + man_juggling: { + keywords: [ "juggle", "balance", "skill", "multitask" ], + char: "\ud83e\udd39\u200d\u2642\ufe0f", + fitzpatrick_scale: true, + category: "activity" + }, + microphone: { + keywords: [ "sound", "music", "PA", "sing", "talkshow" ], + char: "\ud83c\udfa4", + fitzpatrick_scale: false, + category: "activity" + }, + headphones: { + keywords: [ "music", "score", "gadgets" ], + char: "\ud83c\udfa7", + fitzpatrick_scale: false, + category: "activity" + }, + musical_score: { + keywords: [ "treble", "clef", "compose" ], + char: "\ud83c\udfbc", + fitzpatrick_scale: false, + category: "activity" + }, + musical_keyboard: { + keywords: [ "piano", "instrument", "compose" ], + char: "\ud83c\udfb9", + fitzpatrick_scale: false, + category: "activity" + }, + drum: { + keywords: [ "music", "instrument", "drumsticks", "snare" ], + char: "\ud83e\udd41", + fitzpatrick_scale: false, + category: "activity" + }, + saxophone: { + keywords: [ "music", "instrument", "jazz", "blues" ], + char: "\ud83c\udfb7", + fitzpatrick_scale: false, + category: "activity" + }, + trumpet: { + keywords: [ "music", "brass" ], + char: "\ud83c\udfba", + fitzpatrick_scale: false, + category: "activity" + }, + guitar: { + keywords: [ "music", "instrument" ], + char: "\ud83c\udfb8", + fitzpatrick_scale: false, + category: "activity" + }, + violin: { + keywords: [ "music", "instrument", "orchestra", "symphony" ], + char: "\ud83c\udfbb", + fitzpatrick_scale: false, + category: "activity" + }, + clapper: { + keywords: [ "movie", "film", "record" ], + char: "\ud83c\udfac", + fitzpatrick_scale: false, + category: "activity" + }, + video_game: { + keywords: [ "play", "console", "PS4", "controller" ], + char: "\ud83c\udfae", + fitzpatrick_scale: false, + category: "activity" + }, + space_invader: { + keywords: [ "game", "arcade", "play" ], + char: "\ud83d\udc7e", + fitzpatrick_scale: false, + category: "activity" + }, + dart: { + keywords: [ "game", "play", "bar", "target", "bullseye" ], + char: "\ud83c\udfaf", + fitzpatrick_scale: false, + category: "activity" + }, + game_die: { + keywords: [ "dice", "random", "tabletop", "play", "luck" ], + char: "\ud83c\udfb2", + fitzpatrick_scale: false, + category: "activity" + }, + chess_pawn: { + keywords: [ "expendable" ], + char: "\u265f", + fitzpatrick_scale: false, + category: "activity" + }, + slot_machine: { + keywords: [ "bet", "gamble", "vegas", "fruit machine", "luck", "casino" ], + char: "\ud83c\udfb0", + fitzpatrick_scale: false, + category: "activity" + }, + jigsaw: { + keywords: [ "interlocking", "puzzle", "piece" ], + char: "\ud83e\udde9", + fitzpatrick_scale: false, + category: "activity" + }, + bowling: { + keywords: [ "sports", "fun", "play" ], + char: "\ud83c\udfb3", + fitzpatrick_scale: false, + category: "activity" + }, + red_car: { + keywords: [ "red", "transportation", "vehicle" ], + char: "\ud83d\ude97", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + taxi: { + keywords: [ "uber", "vehicle", "cars", "transportation" ], + char: "\ud83d\ude95", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + blue_car: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude99", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bus: { + keywords: [ "car", "vehicle", "transportation" ], + char: "\ud83d\ude8c", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + trolleybus: { + keywords: [ "bart", "transportation", "vehicle" ], + char: "\ud83d\ude8e", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + racing_car: { + keywords: [ "sports", "race", "fast", "formula", "f1" ], + char: "\ud83c\udfce", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + police_car: { + keywords: [ "vehicle", "cars", "transportation", "law", "legal", "enforcement" ], + char: "\ud83d\ude93", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ambulance: { + keywords: [ "health", "911", "hospital" ], + char: "\ud83d\ude91", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fire_engine: { + keywords: [ "transportation", "cars", "vehicle" ], + char: "\ud83d\ude92", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + minibus: { + keywords: [ "vehicle", "car", "transportation" ], + char: "\ud83d\ude90", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + truck: { + keywords: [ "cars", "transportation" ], + char: "\ud83d\ude9a", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + articulated_lorry: { + keywords: [ "vehicle", "cars", "transportation", "express" ], + char: "\ud83d\ude9b", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tractor: { + keywords: [ "vehicle", "car", "farming", "agriculture" ], + char: "\ud83d\ude9c", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + kick_scooter: { + keywords: [ "vehicle", "kick", "razor" ], + char: "\ud83d\udef4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motorcycle: { + keywords: [ "race", "sports", "fast" ], + char: "\ud83c\udfcd", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bike: { + keywords: [ "sports", "bicycle", "exercise", "hipster" ], + char: "\ud83d\udeb2", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motor_scooter: { + keywords: [ "vehicle", "vespa", "sasha" ], + char: "\ud83d\udef5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rotating_light: { + keywords: [ "police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal" ], + char: "\ud83d\udea8", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_police_car: { + keywords: [ "vehicle", "law", "legal", "enforcement", "911" ], + char: "\ud83d\ude94", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_bus: { + keywords: [ "vehicle", "transportation" ], + char: "\ud83d\ude8d", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_automobile: { + keywords: [ "car", "vehicle", "transportation" ], + char: "\ud83d\ude98", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + oncoming_taxi: { + keywords: [ "vehicle", "cars", "uber" ], + char: "\ud83d\ude96", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + aerial_tramway: { + keywords: [ "transportation", "vehicle", "ski" ], + char: "\ud83d\udea1", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_cableway: { + keywords: [ "transportation", "vehicle", "ski" ], + char: "\ud83d\udea0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + suspension_railway: { + keywords: [ "vehicle", "transportation" ], + char: "\ud83d\ude9f", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + railway_car: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude83", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + train: { + keywords: [ "transportation", "vehicle", "carriage", "public", "travel" ], + char: "\ud83d\ude8b", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + monorail: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude9d", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bullettrain_side: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude84", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bullettrain_front: { + keywords: [ "transportation", "vehicle", "speed", "fast", "public", "travel" ], + char: "\ud83d\ude85", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + light_rail: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude88", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_railway: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude9e", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + steam_locomotive: { + keywords: [ "transportation", "vehicle", "train" ], + char: "\ud83d\ude82", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + train2: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude86", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + metro: { + keywords: [ "transportation", "blue-square", "mrt", "underground", "tube" ], + char: "\ud83d\ude87", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tram: { + keywords: [ "transportation", "vehicle" ], + char: "\ud83d\ude8a", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + station: { + keywords: [ "transportation", "vehicle", "public" ], + char: "\ud83d\ude89", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flying_saucer: { + keywords: [ "transportation", "vehicle", "ufo" ], + char: "\ud83d\udef8", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + helicopter: { + keywords: [ "transportation", "vehicle", "fly" ], + char: "\ud83d\ude81", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + small_airplane: { + keywords: [ "flight", "transportation", "fly", "vehicle" ], + char: "\ud83d\udee9", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + airplane: { + keywords: [ "vehicle", "transportation", "flight", "fly" ], + char: "\u2708\ufe0f", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flight_departure: { + keywords: [ "airport", "flight", "landing" ], + char: "\ud83d\udeeb", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + flight_arrival: { + keywords: [ "airport", "flight", "boarding" ], + char: "\ud83d\udeec", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sailboat: { + keywords: [ "ship", "summer", "transportation", "water", "sailing" ], + char: "\u26f5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motor_boat: { + keywords: [ "ship" ], + char: "\ud83d\udee5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + speedboat: { + keywords: [ "ship", "transportation", "vehicle", "summer" ], + char: "\ud83d\udea4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ferry: { + keywords: [ "boat", "ship", "yacht" ], + char: "\u26f4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + passenger_ship: { + keywords: [ "yacht", "cruise", "ferry" ], + char: "\ud83d\udef3", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rocket: { + keywords: [ "launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly" ], + char: "\ud83d\ude80", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + artificial_satellite: { + keywords: [ "communication", "gps", "orbit", "spaceflight", "NASA", "ISS" ], + char: "\ud83d\udef0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + seat: { + keywords: [ "sit", "airplane", "transport", "bus", "flight", "fly" ], + char: "\ud83d\udcba", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + canoe: { + keywords: [ "boat", "paddle", "water", "ship" ], + char: "\ud83d\udef6", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + anchor: { + keywords: [ "ship", "ferry", "sea", "boat" ], + char: "\u2693", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + construction: { + keywords: [ "wip", "progress", "caution", "warning" ], + char: "\ud83d\udea7", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fuelpump: { + keywords: [ "gas station", "petroleum" ], + char: "\u26fd", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + busstop: { + keywords: [ "transportation", "wait" ], + char: "\ud83d\ude8f", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + vertical_traffic_light: { + keywords: [ "transportation", "driving" ], + char: "\ud83d\udea6", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + traffic_light: { + keywords: [ "transportation", "signal" ], + char: "\ud83d\udea5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + checkered_flag: { + keywords: [ "contest", "finishline", "race", "gokart" ], + char: "\ud83c\udfc1", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ship: { + keywords: [ "transportation", "titanic", "deploy" ], + char: "\ud83d\udea2", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + ferris_wheel: { + keywords: [ "photo", "carnival", "londoneye" ], + char: "\ud83c\udfa1", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + roller_coaster: { + keywords: [ "carnival", "playground", "photo", "fun" ], + char: "\ud83c\udfa2", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + carousel_horse: { + keywords: [ "photo", "carnival" ], + char: "\ud83c\udfa0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + building_construction: { + keywords: [ "wip", "working", "progress" ], + char: "\ud83c\udfd7", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + foggy: { + keywords: [ "photo", "mountain" ], + char: "\ud83c\udf01", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tokyo_tower: { + keywords: [ "photo", "japanese" ], + char: "\ud83d\uddfc", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + factory: { + keywords: [ "building", "industry", "pollution", "smoke" ], + char: "\ud83c\udfed", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fountain: { + keywords: [ "photo", "summer", "water", "fresh" ], + char: "\u26f2", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rice_scene: { + keywords: [ "photo", "japan", "asia", "tsukimi" ], + char: "\ud83c\udf91", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain: { + keywords: [ "photo", "nature", "environment" ], + char: "\u26f0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mountain_snow: { + keywords: [ "photo", "nature", "environment", "winter", "cold" ], + char: "\ud83c\udfd4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mount_fuji: { + keywords: [ "photo", "mountain", "nature", "japanese" ], + char: "\ud83d\uddfb", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + volcano: { + keywords: [ "photo", "nature", "disaster" ], + char: "\ud83c\udf0b", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + japan: { + keywords: [ "nation", "country", "japanese", "asia" ], + char: "\ud83d\uddfe", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + camping: { + keywords: [ "photo", "outdoors", "tent" ], + char: "\ud83c\udfd5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + tent: { + keywords: [ "photo", "camping", "outdoors" ], + char: "\u26fa", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + national_park: { + keywords: [ "photo", "environment", "nature" ], + char: "\ud83c\udfde", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + motorway: { + keywords: [ "road", "cupertino", "interstate", "highway" ], + char: "\ud83d\udee3", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + railway_track: { + keywords: [ "train", "transportation" ], + char: "\ud83d\udee4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sunrise: { + keywords: [ "morning", "view", "vacation", "photo" ], + char: "\ud83c\udf05", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sunrise_over_mountains: { + keywords: [ "view", "vacation", "photo" ], + char: "\ud83c\udf04", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + desert: { + keywords: [ "photo", "warm", "saharah" ], + char: "\ud83c\udfdc", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + beach_umbrella: { + keywords: [ "weather", "summer", "sunny", "sand", "mojito" ], + char: "\ud83c\udfd6", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + desert_island: { + keywords: [ "photo", "tropical", "mojito" ], + char: "\ud83c\udfdd", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + city_sunrise: { + keywords: [ "photo", "good morning", "dawn" ], + char: "\ud83c\udf07", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + city_sunset: { + keywords: [ "photo", "evening", "sky", "buildings" ], + char: "\ud83c\udf06", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + cityscape: { + keywords: [ "photo", "night life", "urban" ], + char: "\ud83c\udfd9", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + night_with_stars: { + keywords: [ "evening", "city", "downtown" ], + char: "\ud83c\udf03", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bridge_at_night: { + keywords: [ "photo", "sanfrancisco" ], + char: "\ud83c\udf09", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + milky_way: { + keywords: [ "photo", "space", "stars" ], + char: "\ud83c\udf0c", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + stars: { + keywords: [ "night", "photo" ], + char: "\ud83c\udf20", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + sparkler: { + keywords: [ "stars", "night", "shine" ], + char: "\ud83c\udf87", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + fireworks: { + keywords: [ "photo", "festival", "carnival", "congratulations" ], + char: "\ud83c\udf86", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + rainbow: { + keywords: [ "nature", "happy", "unicorn_face", "photo", "sky", "spring" ], + char: "\ud83c\udf08", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + houses: { + keywords: [ "buildings", "photo" ], + char: "\ud83c\udfd8", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + european_castle: { + keywords: [ "building", "royalty", "history" ], + char: "\ud83c\udff0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + japanese_castle: { + keywords: [ "photo", "building" ], + char: "\ud83c\udfef", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + stadium: { + keywords: [ "photo", "place", "sports", "concert", "venue" ], + char: "\ud83c\udfdf", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + statue_of_liberty: { + keywords: [ "american", "newyork" ], + char: "\ud83d\uddfd", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + house: { + keywords: [ "building", "home" ], + char: "\ud83c\udfe0", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + house_with_garden: { + keywords: [ "home", "plant", "nature" ], + char: "\ud83c\udfe1", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + derelict_house: { + keywords: [ "abandon", "evict", "broken", "building" ], + char: "\ud83c\udfda", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + office: { + keywords: [ "building", "bureau", "work" ], + char: "\ud83c\udfe2", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + department_store: { + keywords: [ "building", "shopping", "mall" ], + char: "\ud83c\udfec", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + post_office: { + keywords: [ "building", "envelope", "communication" ], + char: "\ud83c\udfe3", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + european_post_office: { + keywords: [ "building", "email" ], + char: "\ud83c\udfe4", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + hospital: { + keywords: [ "building", "health", "surgery", "doctor" ], + char: "\ud83c\udfe5", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + bank: { + keywords: [ "building", "money", "sales", "cash", "business", "enterprise" ], + char: "\ud83c\udfe6", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + hotel: { + keywords: [ "building", "accomodation", "checkin" ], + char: "\ud83c\udfe8", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + convenience_store: { + keywords: [ "building", "shopping", "groceries" ], + char: "\ud83c\udfea", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + school: { + keywords: [ "building", "student", "education", "learn", "teach" ], + char: "\ud83c\udfeb", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + love_hotel: { + keywords: [ "like", "affection", "dating" ], + char: "\ud83c\udfe9", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + wedding: { + keywords: [ "love", "like", "affection", "couple", "marriage", "bride", "groom" ], + char: "\ud83d\udc92", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + classical_building: { + keywords: [ "art", "culture", "history" ], + char: "\ud83c\udfdb", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + church: { + keywords: [ "building", "religion", "christ" ], + char: "\u26ea", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + mosque: { + keywords: [ "islam", "worship", "minaret" ], + char: "\ud83d\udd4c", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + synagogue: { + keywords: [ "judaism", "worship", "temple", "jewish" ], + char: "\ud83d\udd4d", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + kaaba: { + keywords: [ "mecca", "mosque", "islam" ], + char: "\ud83d\udd4b", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + shinto_shrine: { + keywords: [ "temple", "japan", "kyoto" ], + char: "\u26e9", + fitzpatrick_scale: false, + category: "travel_and_places" + }, + watch: { + keywords: [ "time", "accessories" ], + char: "\u231a", + fitzpatrick_scale: false, + category: "objects" + }, + iphone: { + keywords: [ "technology", "apple", "gadgets", "dial" ], + char: "\ud83d\udcf1", + fitzpatrick_scale: false, + category: "objects" + }, + calling: { + keywords: [ "iphone", "incoming" ], + char: "\ud83d\udcf2", + fitzpatrick_scale: false, + category: "objects" + }, + computer: { + keywords: [ "technology", "laptop", "screen", "display", "monitor" ], + char: "\ud83d\udcbb", + fitzpatrick_scale: false, + category: "objects" + }, + keyboard: { + keywords: [ "technology", "computer", "type", "input", "text" ], + char: "\u2328", + fitzpatrick_scale: false, + category: "objects" + }, + desktop_computer: { + keywords: [ "technology", "computing", "screen" ], + char: "\ud83d\udda5", + fitzpatrick_scale: false, + category: "objects" + }, + printer: { + keywords: [ "paper", "ink" ], + char: "\ud83d\udda8", + fitzpatrick_scale: false, + category: "objects" + }, + computer_mouse: { + keywords: [ "click" ], + char: "\ud83d\uddb1", + fitzpatrick_scale: false, + category: "objects" + }, + trackball: { + keywords: [ "technology", "trackpad" ], + char: "\ud83d\uddb2", + fitzpatrick_scale: false, + category: "objects" + }, + joystick: { + keywords: [ "game", "play" ], + char: "\ud83d\udd79", + fitzpatrick_scale: false, + category: "objects" + }, + clamp: { + keywords: [ "tool" ], + char: "\ud83d\udddc", + fitzpatrick_scale: false, + category: "objects" + }, + minidisc: { + keywords: [ "technology", "record", "data", "disk", "90s" ], + char: "\ud83d\udcbd", + fitzpatrick_scale: false, + category: "objects" + }, + floppy_disk: { + keywords: [ "oldschool", "technology", "save", "90s", "80s" ], + char: "\ud83d\udcbe", + fitzpatrick_scale: false, + category: "objects" + }, + cd: { + keywords: [ "technology", "dvd", "disk", "disc", "90s" ], + char: "\ud83d\udcbf", + fitzpatrick_scale: false, + category: "objects" + }, + dvd: { + keywords: [ "cd", "disk", "disc" ], + char: "\ud83d\udcc0", + fitzpatrick_scale: false, + category: "objects" + }, + vhs: { + keywords: [ "record", "video", "oldschool", "90s", "80s" ], + char: "\ud83d\udcfc", + fitzpatrick_scale: false, + category: "objects" + }, + camera: { + keywords: [ "gadgets", "photography" ], + char: "\ud83d\udcf7", + fitzpatrick_scale: false, + category: "objects" + }, + camera_flash: { + keywords: [ "photography", "gadgets" ], + char: "\ud83d\udcf8", + fitzpatrick_scale: false, + category: "objects" + }, + video_camera: { + keywords: [ "film", "record" ], + char: "\ud83d\udcf9", + fitzpatrick_scale: false, + category: "objects" + }, + movie_camera: { + keywords: [ "film", "record" ], + char: "\ud83c\udfa5", + fitzpatrick_scale: false, + category: "objects" + }, + film_projector: { + keywords: [ "video", "tape", "record", "movie" ], + char: "\ud83d\udcfd", + fitzpatrick_scale: false, + category: "objects" + }, + film_strip: { + keywords: [ "movie" ], + char: "\ud83c\udf9e", + fitzpatrick_scale: false, + category: "objects" + }, + telephone_receiver: { + keywords: [ "technology", "communication", "dial" ], + char: "\ud83d\udcde", + fitzpatrick_scale: false, + category: "objects" + }, + phone: { + keywords: [ "technology", "communication", "dial", "telephone" ], + char: "\u260e\ufe0f", + fitzpatrick_scale: false, + category: "objects" + }, + pager: { + keywords: [ "bbcall", "oldschool", "90s" ], + char: "\ud83d\udcdf", + fitzpatrick_scale: false, + category: "objects" + }, + fax: { + keywords: [ "communication", "technology" ], + char: "\ud83d\udce0", + fitzpatrick_scale: false, + category: "objects" + }, + tv: { + keywords: [ "technology", "program", "oldschool", "show", "television" ], + char: "\ud83d\udcfa", + fitzpatrick_scale: false, + category: "objects" + }, + radio: { + keywords: [ "communication", "music", "podcast", "program" ], + char: "\ud83d\udcfb", + fitzpatrick_scale: false, + category: "objects" + }, + studio_microphone: { + keywords: [ "sing", "recording", "artist", "talkshow" ], + char: "\ud83c\udf99", + fitzpatrick_scale: false, + category: "objects" + }, + level_slider: { + keywords: [ "scale" ], + char: "\ud83c\udf9a", + fitzpatrick_scale: false, + category: "objects" + }, + control_knobs: { + keywords: [ "dial" ], + char: "\ud83c\udf9b", + fitzpatrick_scale: false, + category: "objects" + }, + compass: { + keywords: [ "magnetic", "navigation", "orienteering" ], + char: "\ud83e\udded", + fitzpatrick_scale: false, + category: "objects" + }, + stopwatch: { + keywords: [ "time", "deadline" ], + char: "\u23f1", + fitzpatrick_scale: false, + category: "objects" + }, + timer_clock: { + keywords: [ "alarm" ], + char: "\u23f2", + fitzpatrick_scale: false, + category: "objects" + }, + alarm_clock: { + keywords: [ "time", "wake" ], + char: "\u23f0", + fitzpatrick_scale: false, + category: "objects" + }, + mantelpiece_clock: { + keywords: [ "time" ], + char: "\ud83d\udd70", + fitzpatrick_scale: false, + category: "objects" + }, + hourglass_flowing_sand: { + keywords: [ "oldschool", "time", "countdown" ], + char: "\u23f3", + fitzpatrick_scale: false, + category: "objects" + }, + hourglass: { + keywords: [ "time", "clock", "oldschool", "limit", "exam", "quiz", "test" ], + char: "\u231b", + fitzpatrick_scale: false, + category: "objects" + }, + satellite: { + keywords: [ "communication", "future", "radio", "space" ], + char: "\ud83d\udce1", + fitzpatrick_scale: false, + category: "objects" + }, + battery: { + keywords: [ "power", "energy", "sustain" ], + char: "\ud83d\udd0b", + fitzpatrick_scale: false, + category: "objects" + }, + electric_plug: { + keywords: [ "charger", "power" ], + char: "\ud83d\udd0c", + fitzpatrick_scale: false, + category: "objects" + }, + bulb: { + keywords: [ "light", "electricity", "idea" ], + char: "\ud83d\udca1", + fitzpatrick_scale: false, + category: "objects" + }, + flashlight: { + keywords: [ "dark", "camping", "sight", "night" ], + char: "\ud83d\udd26", + fitzpatrick_scale: false, + category: "objects" + }, + candle: { + keywords: [ "fire", "wax" ], + char: "\ud83d\udd6f", + fitzpatrick_scale: false, + category: "objects" + }, + fire_extinguisher: { + keywords: [ "quench" ], + char: "\ud83e\uddef", + fitzpatrick_scale: false, + category: "objects" + }, + wastebasket: { + keywords: [ "bin", "trash", "rubbish", "garbage", "toss" ], + char: "\ud83d\uddd1", + fitzpatrick_scale: false, + category: "objects" + }, + oil_drum: { + keywords: [ "barrell" ], + char: "\ud83d\udee2", + fitzpatrick_scale: false, + category: "objects" + }, + money_with_wings: { + keywords: [ "dollar", "bills", "payment", "sale" ], + char: "\ud83d\udcb8", + fitzpatrick_scale: false, + category: "objects" + }, + dollar: { + keywords: [ "money", "sales", "bill", "currency" ], + char: "\ud83d\udcb5", + fitzpatrick_scale: false, + category: "objects" + }, + yen: { + keywords: [ "money", "sales", "japanese", "dollar", "currency" ], + char: "\ud83d\udcb4", + fitzpatrick_scale: false, + category: "objects" + }, + euro: { + keywords: [ "money", "sales", "dollar", "currency" ], + char: "\ud83d\udcb6", + fitzpatrick_scale: false, + category: "objects" + }, + pound: { + keywords: [ "british", "sterling", "money", "sales", "bills", "uk", "england", "currency" ], + char: "\ud83d\udcb7", + fitzpatrick_scale: false, + category: "objects" + }, + moneybag: { + keywords: [ "dollar", "payment", "coins", "sale" ], + char: "\ud83d\udcb0", + fitzpatrick_scale: false, + category: "objects" + }, + credit_card: { + keywords: [ "money", "sales", "dollar", "bill", "payment", "shopping" ], + char: "\ud83d\udcb3", + fitzpatrick_scale: false, + category: "objects" + }, + gem: { + keywords: [ "blue", "ruby", "diamond", "jewelry" ], + char: "\ud83d\udc8e", + fitzpatrick_scale: false, + category: "objects" + }, + balance_scale: { + keywords: [ "law", "fairness", "weight" ], + char: "\u2696", + fitzpatrick_scale: false, + category: "objects" + }, + toolbox: { + keywords: [ "tools", "diy", "fix", "maintainer", "mechanic" ], + char: "\ud83e\uddf0", + fitzpatrick_scale: false, + category: "objects" + }, + wrench: { + keywords: [ "tools", "diy", "ikea", "fix", "maintainer" ], + char: "\ud83d\udd27", + fitzpatrick_scale: false, + category: "objects" + }, + hammer: { + keywords: [ "tools", "build", "create" ], + char: "\ud83d\udd28", + fitzpatrick_scale: false, + category: "objects" + }, + hammer_and_pick: { + keywords: [ "tools", "build", "create" ], + char: "\u2692", + fitzpatrick_scale: false, + category: "objects" + }, + hammer_and_wrench: { + keywords: [ "tools", "build", "create" ], + char: "\ud83d\udee0", + fitzpatrick_scale: false, + category: "objects" + }, + pick: { + keywords: [ "tools", "dig" ], + char: "\u26cf", + fitzpatrick_scale: false, + category: "objects" + }, + nut_and_bolt: { + keywords: [ "handy", "tools", "fix" ], + char: "\ud83d\udd29", + fitzpatrick_scale: false, + category: "objects" + }, + gear: { + keywords: [ "cog" ], + char: "\u2699", + fitzpatrick_scale: false, + category: "objects" + }, + brick: { + keywords: [ "bricks" ], + char: "\ud83e\uddf1", + fitzpatrick_scale: false, + category: "objects" + }, + chains: { + keywords: [ "lock", "arrest" ], + char: "\u26d3", + fitzpatrick_scale: false, + category: "objects" + }, + magnet: { + keywords: [ "attraction", "magnetic" ], + char: "\ud83e\uddf2", + fitzpatrick_scale: false, + category: "objects" + }, + gun: { + keywords: [ "violence", "weapon", "pistol", "revolver" ], + char: "\ud83d\udd2b", + fitzpatrick_scale: false, + category: "objects" + }, + bomb: { + keywords: [ "boom", "explode", "explosion", "terrorism" ], + char: "\ud83d\udca3", + fitzpatrick_scale: false, + category: "objects" + }, + firecracker: { + keywords: [ "dynamite", "boom", "explode", "explosion", "explosive" ], + char: "\ud83e\udde8", + fitzpatrick_scale: false, + category: "objects" + }, + hocho: { + keywords: [ "knife", "blade", "cutlery", "kitchen", "weapon" ], + char: "\ud83d\udd2a", + fitzpatrick_scale: false, + category: "objects" + }, + dagger: { + keywords: [ "weapon" ], + char: "\ud83d\udde1", + fitzpatrick_scale: false, + category: "objects" + }, + crossed_swords: { + keywords: [ "weapon" ], + char: "\u2694", + fitzpatrick_scale: false, + category: "objects" + }, + shield: { + keywords: [ "protection", "security" ], + char: "\ud83d\udee1", + fitzpatrick_scale: false, + category: "objects" + }, + smoking: { + keywords: [ "kills", "tobacco", "cigarette", "joint", "smoke" ], + char: "\ud83d\udeac", + fitzpatrick_scale: false, + category: "objects" + }, + skull_and_crossbones: { + keywords: [ "poison", "danger", "deadly", "scary", "death", "pirate", "evil" ], + char: "\u2620", + fitzpatrick_scale: false, + category: "objects" + }, + coffin: { + keywords: [ "vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box" ], + char: "\u26b0", + fitzpatrick_scale: false, + category: "objects" + }, + funeral_urn: { + keywords: [ "dead", "die", "death", "rip", "ashes" ], + char: "\u26b1", + fitzpatrick_scale: false, + category: "objects" + }, + amphora: { + keywords: [ "vase", "jar" ], + char: "\ud83c\udffa", + fitzpatrick_scale: false, + category: "objects" + }, + crystal_ball: { + keywords: [ "disco", "party", "magic", "circus", "fortune_teller" ], + char: "\ud83d\udd2e", + fitzpatrick_scale: false, + category: "objects" + }, + prayer_beads: { + keywords: [ "dhikr", "religious" ], + char: "\ud83d\udcff", + fitzpatrick_scale: false, + category: "objects" + }, + nazar_amulet: { + keywords: [ "bead", "charm" ], + char: "\ud83e\uddff", + fitzpatrick_scale: false, + category: "objects" + }, + barber: { + keywords: [ "hair", "salon", "style" ], + char: "\ud83d\udc88", + fitzpatrick_scale: false, + category: "objects" + }, + alembic: { + keywords: [ "distilling", "science", "experiment", "chemistry" ], + char: "\u2697", + fitzpatrick_scale: false, + category: "objects" + }, + telescope: { + keywords: [ "stars", "space", "zoom", "science", "astronomy" ], + char: "\ud83d\udd2d", + fitzpatrick_scale: false, + category: "objects" + }, + microscope: { + keywords: [ "laboratory", "experiment", "zoomin", "science", "study" ], + char: "\ud83d\udd2c", + fitzpatrick_scale: false, + category: "objects" + }, + hole: { + keywords: [ "embarrassing" ], + char: "\ud83d\udd73", + fitzpatrick_scale: false, + category: "objects" + }, + pill: { + keywords: [ "health", "medicine", "doctor", "pharmacy", "drug" ], + char: "\ud83d\udc8a", + fitzpatrick_scale: false, + category: "objects" + }, + syringe: { + keywords: [ "health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse" ], + char: "\ud83d\udc89", + fitzpatrick_scale: false, + category: "objects" + }, + dna: { + keywords: [ "biologist", "genetics", "life" ], + char: "\ud83e\uddec", + fitzpatrick_scale: false, + category: "objects" + }, + microbe: { + keywords: [ "amoeba", "bacteria", "germs" ], + char: "\ud83e\udda0", + fitzpatrick_scale: false, + category: "objects" + }, + petri_dish: { + keywords: [ "bacteria", "biology", "culture", "lab" ], + char: "\ud83e\uddeb", + fitzpatrick_scale: false, + category: "objects" + }, + test_tube: { + keywords: [ "chemistry", "experiment", "lab", "science" ], + char: "\ud83e\uddea", + fitzpatrick_scale: false, + category: "objects" + }, + thermometer: { + keywords: [ "weather", "temperature", "hot", "cold" ], + char: "\ud83c\udf21", + fitzpatrick_scale: false, + category: "objects" + }, + broom: { + keywords: [ "cleaning", "sweeping", "witch" ], + char: "\ud83e\uddf9", + fitzpatrick_scale: false, + category: "objects" + }, + basket: { + keywords: [ "laundry" ], + char: "\ud83e\uddfa", + fitzpatrick_scale: false, + category: "objects" + }, + toilet_paper: { + keywords: [ "roll" ], + char: "\ud83e\uddfb", + fitzpatrick_scale: false, + category: "objects" + }, + label: { + keywords: [ "sale", "tag" ], + char: "\ud83c\udff7", + fitzpatrick_scale: false, + category: "objects" + }, + bookmark: { + keywords: [ "favorite", "label", "save" ], + char: "\ud83d\udd16", + fitzpatrick_scale: false, + category: "objects" + }, + toilet: { + keywords: [ "restroom", "wc", "washroom", "bathroom", "potty" ], + char: "\ud83d\udebd", + fitzpatrick_scale: false, + category: "objects" + }, + shower: { + keywords: [ "clean", "water", "bathroom" ], + char: "\ud83d\udebf", + fitzpatrick_scale: false, + category: "objects" + }, + bathtub: { + keywords: [ "clean", "shower", "bathroom" ], + char: "\ud83d\udec1", + fitzpatrick_scale: false, + category: "objects" + }, + soap: { + keywords: [ "bar", "bathing", "cleaning", "lather" ], + char: "\ud83e\uddfc", + fitzpatrick_scale: false, + category: "objects" + }, + sponge: { + keywords: [ "absorbing", "cleaning", "porous" ], + char: "\ud83e\uddfd", + fitzpatrick_scale: false, + category: "objects" + }, + lotion_bottle: { + keywords: [ "moisturizer", "sunscreen" ], + char: "\ud83e\uddf4", + fitzpatrick_scale: false, + category: "objects" + }, + key: { + keywords: [ "lock", "door", "password" ], + char: "\ud83d\udd11", + fitzpatrick_scale: false, + category: "objects" + }, + old_key: { + keywords: [ "lock", "door", "password" ], + char: "\ud83d\udddd", + fitzpatrick_scale: false, + category: "objects" + }, + couch_and_lamp: { + keywords: [ "read", "chill" ], + char: "\ud83d\udecb", + fitzpatrick_scale: false, + category: "objects" + }, + sleeping_bed: { + keywords: [ "bed", "rest" ], + char: "\ud83d\udecc", + fitzpatrick_scale: true, + category: "objects" + }, + bed: { + keywords: [ "sleep", "rest" ], + char: "\ud83d\udecf", + fitzpatrick_scale: false, + category: "objects" + }, + door: { + keywords: [ "house", "entry", "exit" ], + char: "\ud83d\udeaa", + fitzpatrick_scale: false, + category: "objects" + }, + bellhop_bell: { + keywords: [ "service" ], + char: "\ud83d\udece", + fitzpatrick_scale: false, + category: "objects" + }, + teddy_bear: { + keywords: [ "plush", "stuffed" ], + char: "\ud83e\uddf8", + fitzpatrick_scale: false, + category: "objects" + }, + framed_picture: { + keywords: [ "photography" ], + char: "\ud83d\uddbc", + fitzpatrick_scale: false, + category: "objects" + }, + world_map: { + keywords: [ "location", "direction" ], + char: "\ud83d\uddfa", + fitzpatrick_scale: false, + category: "objects" + }, + parasol_on_ground: { + keywords: [ "weather", "summer" ], + char: "\u26f1", + fitzpatrick_scale: false, + category: "objects" + }, + moyai: { + keywords: [ "rock", "easter island", "moai" ], + char: "\ud83d\uddff", + fitzpatrick_scale: false, + category: "objects" + }, + shopping: { + keywords: [ "mall", "buy", "purchase" ], + char: "\ud83d\udecd", + fitzpatrick_scale: false, + category: "objects" + }, + shopping_cart: { + keywords: [ "trolley" ], + char: "\ud83d\uded2", + fitzpatrick_scale: false, + category: "objects" + }, + balloon: { + keywords: [ "party", "celebration", "birthday", "circus" ], + char: "\ud83c\udf88", + fitzpatrick_scale: false, + category: "objects" + }, + flags: { + keywords: [ "fish", "japanese", "koinobori", "carp", "banner" ], + char: "\ud83c\udf8f", + fitzpatrick_scale: false, + category: "objects" + }, + ribbon: { + keywords: [ "decoration", "pink", "girl", "bowtie" ], + char: "\ud83c\udf80", + fitzpatrick_scale: false, + category: "objects" + }, + gift: { + keywords: [ "present", "birthday", "christmas", "xmas" ], + char: "\ud83c\udf81", + fitzpatrick_scale: false, + category: "objects" + }, + confetti_ball: { + keywords: [ "festival", "party", "birthday", "circus" ], + char: "\ud83c\udf8a", + fitzpatrick_scale: false, + category: "objects" + }, + tada: { + keywords: [ "party", "congratulations", "birthday", "magic", "circus", "celebration" ], + char: "\ud83c\udf89", + fitzpatrick_scale: false, + category: "objects" + }, + dolls: { + keywords: [ "japanese", "toy", "kimono" ], + char: "\ud83c\udf8e", + fitzpatrick_scale: false, + category: "objects" + }, + wind_chime: { + keywords: [ "nature", "ding", "spring", "bell" ], + char: "\ud83c\udf90", + fitzpatrick_scale: false, + category: "objects" + }, + crossed_flags: { + keywords: [ "japanese", "nation", "country", "border" ], + char: "\ud83c\udf8c", + fitzpatrick_scale: false, + category: "objects" + }, + izakaya_lantern: { + keywords: [ "light", "paper", "halloween", "spooky" ], + char: "\ud83c\udfee", + fitzpatrick_scale: false, + category: "objects" + }, + red_envelope: { + keywords: [ "gift" ], + char: "\ud83e\udde7", + fitzpatrick_scale: false, + category: "objects" + }, + email: { + keywords: [ "letter", "postal", "inbox", "communication" ], + char: "\u2709\ufe0f", + fitzpatrick_scale: false, + category: "objects" + }, + envelope_with_arrow: { + keywords: [ "email", "communication" ], + char: "\ud83d\udce9", + fitzpatrick_scale: false, + category: "objects" + }, + incoming_envelope: { + keywords: [ "email", "inbox" ], + char: "\ud83d\udce8", + fitzpatrick_scale: false, + category: "objects" + }, + "e-mail": { + keywords: [ "communication", "inbox" ], + char: "\ud83d\udce7", + fitzpatrick_scale: false, + category: "objects" + }, + love_letter: { + keywords: [ "email", "like", "affection", "envelope", "valentines" ], + char: "\ud83d\udc8c", + fitzpatrick_scale: false, + category: "objects" + }, + postbox: { + keywords: [ "email", "letter", "envelope" ], + char: "\ud83d\udcee", + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_closed: { + keywords: [ "email", "communication", "inbox" ], + char: "\ud83d\udcea", + fitzpatrick_scale: false, + category: "objects" + }, + mailbox: { + keywords: [ "email", "inbox", "communication" ], + char: "\ud83d\udceb", + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_with_mail: { + keywords: [ "email", "inbox", "communication" ], + char: "\ud83d\udcec", + fitzpatrick_scale: false, + category: "objects" + }, + mailbox_with_no_mail: { + keywords: [ "email", "inbox" ], + char: "\ud83d\udced", + fitzpatrick_scale: false, + category: "objects" + }, + package: { + keywords: [ "mail", "gift", "cardboard", "box", "moving" ], + char: "\ud83d\udce6", + fitzpatrick_scale: false, + category: "objects" + }, + postal_horn: { + keywords: [ "instrument", "music" ], + char: "\ud83d\udcef", + fitzpatrick_scale: false, + category: "objects" + }, + inbox_tray: { + keywords: [ "email", "documents" ], + char: "\ud83d\udce5", + fitzpatrick_scale: false, + category: "objects" + }, + outbox_tray: { + keywords: [ "inbox", "email" ], + char: "\ud83d\udce4", + fitzpatrick_scale: false, + category: "objects" + }, + scroll: { + keywords: [ "documents", "ancient", "history", "paper" ], + char: "\ud83d\udcdc", + fitzpatrick_scale: false, + category: "objects" + }, + page_with_curl: { + keywords: [ "documents", "office", "paper" ], + char: "\ud83d\udcc3", + fitzpatrick_scale: false, + category: "objects" + }, + bookmark_tabs: { + keywords: [ "favorite", "save", "order", "tidy" ], + char: "\ud83d\udcd1", + fitzpatrick_scale: false, + category: "objects" + }, + receipt: { + keywords: [ "accounting", "expenses" ], + char: "\ud83e\uddfe", + fitzpatrick_scale: false, + category: "objects" + }, + bar_chart: { + keywords: [ "graph", "presentation", "stats" ], + char: "\ud83d\udcca", + fitzpatrick_scale: false, + category: "objects" + }, + chart_with_upwards_trend: { + keywords: [ "graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success" ], + char: "\ud83d\udcc8", + fitzpatrick_scale: false, + category: "objects" + }, + chart_with_downwards_trend: { + keywords: [ "graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure" ], + char: "\ud83d\udcc9", + fitzpatrick_scale: false, + category: "objects" + }, + page_facing_up: { + keywords: [ "documents", "office", "paper", "information" ], + char: "\ud83d\udcc4", + fitzpatrick_scale: false, + category: "objects" + }, + date: { + keywords: [ "calendar", "schedule" ], + char: "\ud83d\udcc5", + fitzpatrick_scale: false, + category: "objects" + }, + calendar: { + keywords: [ "schedule", "date", "planning" ], + char: "\ud83d\udcc6", + fitzpatrick_scale: false, + category: "objects" + }, + spiral_calendar: { + keywords: [ "date", "schedule", "planning" ], + char: "\ud83d\uddd3", + fitzpatrick_scale: false, + category: "objects" + }, + card_index: { + keywords: [ "business", "stationery" ], + char: "\ud83d\udcc7", + fitzpatrick_scale: false, + category: "objects" + }, + card_file_box: { + keywords: [ "business", "stationery" ], + char: "\ud83d\uddc3", + fitzpatrick_scale: false, + category: "objects" + }, + ballot_box: { + keywords: [ "election", "vote" ], + char: "\ud83d\uddf3", + fitzpatrick_scale: false, + category: "objects" + }, + file_cabinet: { + keywords: [ "filing", "organizing" ], + char: "\ud83d\uddc4", + fitzpatrick_scale: false, + category: "objects" + }, + clipboard: { + keywords: [ "stationery", "documents" ], + char: "\ud83d\udccb", + fitzpatrick_scale: false, + category: "objects" + }, + spiral_notepad: { + keywords: [ "memo", "stationery" ], + char: "\ud83d\uddd2", + fitzpatrick_scale: false, + category: "objects" + }, + file_folder: { + keywords: [ "documents", "business", "office" ], + char: "\ud83d\udcc1", + fitzpatrick_scale: false, + category: "objects" + }, + open_file_folder: { + keywords: [ "documents", "load" ], + char: "\ud83d\udcc2", + fitzpatrick_scale: false, + category: "objects" + }, + card_index_dividers: { + keywords: [ "organizing", "business", "stationery" ], + char: "\ud83d\uddc2", + fitzpatrick_scale: false, + category: "objects" + }, + newspaper_roll: { + keywords: [ "press", "headline" ], + char: "\ud83d\uddde", + fitzpatrick_scale: false, + category: "objects" + }, + newspaper: { + keywords: [ "press", "headline" ], + char: "\ud83d\udcf0", + fitzpatrick_scale: false, + category: "objects" + }, + notebook: { + keywords: [ "stationery", "record", "notes", "paper", "study" ], + char: "\ud83d\udcd3", + fitzpatrick_scale: false, + category: "objects" + }, + closed_book: { + keywords: [ "read", "library", "knowledge", "textbook", "learn" ], + char: "\ud83d\udcd5", + fitzpatrick_scale: false, + category: "objects" + }, + green_book: { + keywords: [ "read", "library", "knowledge", "study" ], + char: "\ud83d\udcd7", + fitzpatrick_scale: false, + category: "objects" + }, + blue_book: { + keywords: [ "read", "library", "knowledge", "learn", "study" ], + char: "\ud83d\udcd8", + fitzpatrick_scale: false, + category: "objects" + }, + orange_book: { + keywords: [ "read", "library", "knowledge", "textbook", "study" ], + char: "\ud83d\udcd9", + fitzpatrick_scale: false, + category: "objects" + }, + notebook_with_decorative_cover: { + keywords: [ "classroom", "notes", "record", "paper", "study" ], + char: "\ud83d\udcd4", + fitzpatrick_scale: false, + category: "objects" + }, + ledger: { + keywords: [ "notes", "paper" ], + char: "\ud83d\udcd2", + fitzpatrick_scale: false, + category: "objects" + }, + books: { + keywords: [ "literature", "library", "study" ], + char: "\ud83d\udcda", + fitzpatrick_scale: false, + category: "objects" + }, + open_book: { + keywords: [ "book", "read", "library", "knowledge", "literature", "learn", "study" ], + char: "\ud83d\udcd6", + fitzpatrick_scale: false, + category: "objects" + }, + safety_pin: { + keywords: [ "diaper" ], + char: "\ud83e\uddf7", + fitzpatrick_scale: false, + category: "objects" + }, + link: { + keywords: [ "rings", "url" ], + char: "\ud83d\udd17", + fitzpatrick_scale: false, + category: "objects" + }, + paperclip: { + keywords: [ "documents", "stationery" ], + char: "\ud83d\udcce", + fitzpatrick_scale: false, + category: "objects" + }, + paperclips: { + keywords: [ "documents", "stationery" ], + char: "\ud83d\udd87", + fitzpatrick_scale: false, + category: "objects" + }, + scissors: { + keywords: [ "stationery", "cut" ], + char: "\u2702\ufe0f", + fitzpatrick_scale: false, + category: "objects" + }, + triangular_ruler: { + keywords: [ "stationery", "math", "architect", "sketch" ], + char: "\ud83d\udcd0", + fitzpatrick_scale: false, + category: "objects" + }, + straight_ruler: { + keywords: [ "stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch" ], + char: "\ud83d\udccf", + fitzpatrick_scale: false, + category: "objects" + }, + abacus: { + keywords: [ "calculation" ], + char: "\ud83e\uddee", + fitzpatrick_scale: false, + category: "objects" + }, + pushpin: { + keywords: [ "stationery", "mark", "here" ], + char: "\ud83d\udccc", + fitzpatrick_scale: false, + category: "objects" + }, + round_pushpin: { + keywords: [ "stationery", "location", "map", "here" ], + char: "\ud83d\udccd", + fitzpatrick_scale: false, + category: "objects" + }, + triangular_flag_on_post: { + keywords: [ "mark", "milestone", "place" ], + char: "\ud83d\udea9", + fitzpatrick_scale: false, + category: "objects" + }, + white_flag: { + keywords: [ "losing", "loser", "lost", "surrender", "give up", "fail" ], + char: "\ud83c\udff3", + fitzpatrick_scale: false, + category: "objects" + }, + black_flag: { + keywords: [ "pirate" ], + char: "\ud83c\udff4", + fitzpatrick_scale: false, + category: "objects" + }, + rainbow_flag: { + keywords: [ "flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender" ], + char: "\ud83c\udff3\ufe0f\u200d\ud83c\udf08", + fitzpatrick_scale: false, + category: "objects" + }, + closed_lock_with_key: { + keywords: [ "security", "privacy" ], + char: "\ud83d\udd10", + fitzpatrick_scale: false, + category: "objects" + }, + lock: { + keywords: [ "security", "password", "padlock" ], + char: "\ud83d\udd12", + fitzpatrick_scale: false, + category: "objects" + }, + unlock: { + keywords: [ "privacy", "security" ], + char: "\ud83d\udd13", + fitzpatrick_scale: false, + category: "objects" + }, + lock_with_ink_pen: { + keywords: [ "security", "secret" ], + char: "\ud83d\udd0f", + fitzpatrick_scale: false, + category: "objects" + }, + pen: { + keywords: [ "stationery", "writing", "write" ], + char: "\ud83d\udd8a", + fitzpatrick_scale: false, + category: "objects" + }, + fountain_pen: { + keywords: [ "stationery", "writing", "write" ], + char: "\ud83d\udd8b", + fitzpatrick_scale: false, + category: "objects" + }, + black_nib: { + keywords: [ "pen", "stationery", "writing", "write" ], + char: "\u2712\ufe0f", + fitzpatrick_scale: false, + category: "objects" + }, + memo: { + keywords: [ "write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose" ], + char: "\ud83d\udcdd", + fitzpatrick_scale: false, + category: "objects" + }, + pencil2: { + keywords: [ "stationery", "write", "paper", "writing", "school", "study" ], + char: "\u270f\ufe0f", + fitzpatrick_scale: false, + category: "objects" + }, + crayon: { + keywords: [ "drawing", "creativity" ], + char: "\ud83d\udd8d", + fitzpatrick_scale: false, + category: "objects" + }, + paintbrush: { + keywords: [ "drawing", "creativity", "art" ], + char: "\ud83d\udd8c", + fitzpatrick_scale: false, + category: "objects" + }, + mag: { + keywords: [ "search", "zoom", "find", "detective" ], + char: "\ud83d\udd0d", + fitzpatrick_scale: false, + category: "objects" + }, + mag_right: { + keywords: [ "search", "zoom", "find", "detective" ], + char: "\ud83d\udd0e", + fitzpatrick_scale: false, + category: "objects" + }, + heart: { + keywords: [ "love", "like", "valentines" ], + char: "\u2764\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + orange_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83e\udde1", + fitzpatrick_scale: false, + category: "symbols" + }, + yellow_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc9b", + fitzpatrick_scale: false, + category: "symbols" + }, + green_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc9a", + fitzpatrick_scale: false, + category: "symbols" + }, + blue_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc99", + fitzpatrick_scale: false, + category: "symbols" + }, + purple_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc9c", + fitzpatrick_scale: false, + category: "symbols" + }, + black_heart: { + keywords: [ "evil" ], + char: "\ud83d\udda4", + fitzpatrick_scale: false, + category: "symbols" + }, + broken_heart: { + keywords: [ "sad", "sorry", "break", "heart", "heartbreak" ], + char: "\ud83d\udc94", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_heart_exclamation: { + keywords: [ "decoration", "love" ], + char: "\u2763", + fitzpatrick_scale: false, + category: "symbols" + }, + two_hearts: { + keywords: [ "love", "like", "affection", "valentines", "heart" ], + char: "\ud83d\udc95", + fitzpatrick_scale: false, + category: "symbols" + }, + revolving_hearts: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc9e", + fitzpatrick_scale: false, + category: "symbols" + }, + heartbeat: { + keywords: [ "love", "like", "affection", "valentines", "pink", "heart" ], + char: "\ud83d\udc93", + fitzpatrick_scale: false, + category: "symbols" + }, + heartpulse: { + keywords: [ "like", "love", "affection", "valentines", "pink" ], + char: "\ud83d\udc97", + fitzpatrick_scale: false, + category: "symbols" + }, + sparkling_heart: { + keywords: [ "love", "like", "affection", "valentines" ], + char: "\ud83d\udc96", + fitzpatrick_scale: false, + category: "symbols" + }, + cupid: { + keywords: [ "love", "like", "heart", "affection", "valentines" ], + char: "\ud83d\udc98", + fitzpatrick_scale: false, + category: "symbols" + }, + gift_heart: { + keywords: [ "love", "valentines" ], + char: "\ud83d\udc9d", + fitzpatrick_scale: false, + category: "symbols" + }, + heart_decoration: { + keywords: [ "purple-square", "love", "like" ], + char: "\ud83d\udc9f", + fitzpatrick_scale: false, + category: "symbols" + }, + peace_symbol: { + keywords: [ "hippie" ], + char: "\u262e", + fitzpatrick_scale: false, + category: "symbols" + }, + latin_cross: { + keywords: [ "christianity" ], + char: "\u271d", + fitzpatrick_scale: false, + category: "symbols" + }, + star_and_crescent: { + keywords: [ "islam" ], + char: "\u262a", + fitzpatrick_scale: false, + category: "symbols" + }, + om: { + keywords: [ "hinduism", "buddhism", "sikhism", "jainism" ], + char: "\ud83d\udd49", + fitzpatrick_scale: false, + category: "symbols" + }, + wheel_of_dharma: { + keywords: [ "hinduism", "buddhism", "sikhism", "jainism" ], + char: "\u2638", + fitzpatrick_scale: false, + category: "symbols" + }, + star_of_david: { + keywords: [ "judaism" ], + char: "\u2721", + fitzpatrick_scale: false, + category: "symbols" + }, + six_pointed_star: { + keywords: [ "purple-square", "religion", "jewish", "hexagram" ], + char: "\ud83d\udd2f", + fitzpatrick_scale: false, + category: "symbols" + }, + menorah: { + keywords: [ "hanukkah", "candles", "jewish" ], + char: "\ud83d\udd4e", + fitzpatrick_scale: false, + category: "symbols" + }, + yin_yang: { + keywords: [ "balance" ], + char: "\u262f", + fitzpatrick_scale: false, + category: "symbols" + }, + orthodox_cross: { + keywords: [ "suppedaneum", "religion" ], + char: "\u2626", + fitzpatrick_scale: false, + category: "symbols" + }, + place_of_worship: { + keywords: [ "religion", "church", "temple", "prayer" ], + char: "\ud83d\uded0", + fitzpatrick_scale: false, + category: "symbols" + }, + ophiuchus: { + keywords: [ "sign", "purple-square", "constellation", "astrology" ], + char: "\u26ce", + fitzpatrick_scale: false, + category: "symbols" + }, + aries: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: "\u2648", + fitzpatrick_scale: false, + category: "symbols" + }, + taurus: { + keywords: [ "purple-square", "sign", "zodiac", "astrology" ], + char: "\u2649", + fitzpatrick_scale: false, + category: "symbols" + }, + gemini: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: "\u264a", + fitzpatrick_scale: false, + category: "symbols" + }, + cancer: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: "\u264b", + fitzpatrick_scale: false, + category: "symbols" + }, + leo: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: "\u264c", + fitzpatrick_scale: false, + category: "symbols" + }, + virgo: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: "\u264d", + fitzpatrick_scale: false, + category: "symbols" + }, + libra: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: "\u264e", + fitzpatrick_scale: false, + category: "symbols" + }, + scorpius: { + keywords: [ "sign", "zodiac", "purple-square", "astrology", "scorpio" ], + char: "\u264f", + fitzpatrick_scale: false, + category: "symbols" + }, + sagittarius: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: "\u2650", + fitzpatrick_scale: false, + category: "symbols" + }, + capricorn: { + keywords: [ "sign", "zodiac", "purple-square", "astrology" ], + char: "\u2651", + fitzpatrick_scale: false, + category: "symbols" + }, + aquarius: { + keywords: [ "sign", "purple-square", "zodiac", "astrology" ], + char: "\u2652", + fitzpatrick_scale: false, + category: "symbols" + }, + pisces: { + keywords: [ "purple-square", "sign", "zodiac", "astrology" ], + char: "\u2653", + fitzpatrick_scale: false, + category: "symbols" + }, + id: { + keywords: [ "purple-square", "words" ], + char: "\ud83c\udd94", + fitzpatrick_scale: false, + category: "symbols" + }, + atom_symbol: { + keywords: [ "science", "physics", "chemistry" ], + char: "\u269b", + fitzpatrick_scale: false, + category: "symbols" + }, + u7a7a: { + keywords: [ "kanji", "japanese", "chinese", "empty", "sky", "blue-square" ], + char: "\ud83c\ude33", + fitzpatrick_scale: false, + category: "symbols" + }, + u5272: { + keywords: [ "cut", "divide", "chinese", "kanji", "pink-square" ], + char: "\ud83c\ude39", + fitzpatrick_scale: false, + category: "symbols" + }, + radioactive: { + keywords: [ "nuclear", "danger" ], + char: "\u2622", + fitzpatrick_scale: false, + category: "symbols" + }, + biohazard: { + keywords: [ "danger" ], + char: "\u2623", + fitzpatrick_scale: false, + category: "symbols" + }, + mobile_phone_off: { + keywords: [ "mute", "orange-square", "silence", "quiet" ], + char: "\ud83d\udcf4", + fitzpatrick_scale: false, + category: "symbols" + }, + vibration_mode: { + keywords: [ "orange-square", "phone" ], + char: "\ud83d\udcf3", + fitzpatrick_scale: false, + category: "symbols" + }, + u6709: { + keywords: [ "orange-square", "chinese", "have", "kanji" ], + char: "\ud83c\ude36", + fitzpatrick_scale: false, + category: "symbols" + }, + u7121: { + keywords: [ "nothing", "chinese", "kanji", "japanese", "orange-square" ], + char: "\ud83c\ude1a", + fitzpatrick_scale: false, + category: "symbols" + }, + u7533: { + keywords: [ "chinese", "japanese", "kanji", "orange-square" ], + char: "\ud83c\ude38", + fitzpatrick_scale: false, + category: "symbols" + }, + u55b6: { + keywords: [ "japanese", "opening hours", "orange-square" ], + char: "\ud83c\ude3a", + fitzpatrick_scale: false, + category: "symbols" + }, + u6708: { + keywords: [ "chinese", "month", "moon", "japanese", "orange-square", "kanji" ], + char: "\ud83c\ude37\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + eight_pointed_black_star: { + keywords: [ "orange-square", "shape", "polygon" ], + char: "\u2734\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + vs: { + keywords: [ "words", "orange-square" ], + char: "\ud83c\udd9a", + fitzpatrick_scale: false, + category: "symbols" + }, + accept: { + keywords: [ "ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle" ], + char: "\ud83c\ude51", + fitzpatrick_scale: false, + category: "symbols" + }, + white_flower: { + keywords: [ "japanese", "spring" ], + char: "\ud83d\udcae", + fitzpatrick_scale: false, + category: "symbols" + }, + ideograph_advantage: { + keywords: [ "chinese", "kanji", "obtain", "get", "circle" ], + char: "\ud83c\ude50", + fitzpatrick_scale: false, + category: "symbols" + }, + secret: { + keywords: [ "privacy", "chinese", "sshh", "kanji", "red-circle" ], + char: "\u3299\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + congratulations: { + keywords: [ "chinese", "kanji", "japanese", "red-circle" ], + char: "\u3297\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + u5408: { + keywords: [ "japanese", "chinese", "join", "kanji", "red-square" ], + char: "\ud83c\ude34", + fitzpatrick_scale: false, + category: "symbols" + }, + u6e80: { + keywords: [ "full", "chinese", "japanese", "red-square", "kanji" ], + char: "\ud83c\ude35", + fitzpatrick_scale: false, + category: "symbols" + }, + u7981: { + keywords: [ "kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square" ], + char: "\ud83c\ude32", + fitzpatrick_scale: false, + category: "symbols" + }, + a: { + keywords: [ "red-square", "alphabet", "letter" ], + char: "\ud83c\udd70\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + b: { + keywords: [ "red-square", "alphabet", "letter" ], + char: "\ud83c\udd71\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + ab: { + keywords: [ "red-square", "alphabet" ], + char: "\ud83c\udd8e", + fitzpatrick_scale: false, + category: "symbols" + }, + cl: { + keywords: [ "alphabet", "words", "red-square" ], + char: "\ud83c\udd91", + fitzpatrick_scale: false, + category: "symbols" + }, + o2: { + keywords: [ "alphabet", "red-square", "letter" ], + char: "\ud83c\udd7e\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + sos: { + keywords: [ "help", "red-square", "words", "emergency", "911" ], + char: "\ud83c\udd98", + fitzpatrick_scale: false, + category: "symbols" + }, + no_entry: { + keywords: [ "limit", "security", "privacy", "bad", "denied", "stop", "circle" ], + char: "\u26d4", + fitzpatrick_scale: false, + category: "symbols" + }, + name_badge: { + keywords: [ "fire", "forbid" ], + char: "\ud83d\udcdb", + fitzpatrick_scale: false, + category: "symbols" + }, + no_entry_sign: { + keywords: [ "forbid", "stop", "limit", "denied", "disallow", "circle" ], + char: "\ud83d\udeab", + fitzpatrick_scale: false, + category: "symbols" + }, + x: { + keywords: [ "no", "delete", "remove", "cancel", "red" ], + char: "\u274c", + fitzpatrick_scale: false, + category: "symbols" + }, + o: { + keywords: [ "circle", "round" ], + char: "\u2b55", + fitzpatrick_scale: false, + category: "symbols" + }, + stop_sign: { + keywords: [ "stop" ], + char: "\ud83d\uded1", + fitzpatrick_scale: false, + category: "symbols" + }, + anger: { + keywords: [ "angry", "mad" ], + char: "\ud83d\udca2", + fitzpatrick_scale: false, + category: "symbols" + }, + hotsprings: { + keywords: [ "bath", "warm", "relax" ], + char: "\u2668\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + no_pedestrians: { + keywords: [ "rules", "crossing", "walking", "circle" ], + char: "\ud83d\udeb7", + fitzpatrick_scale: false, + category: "symbols" + }, + do_not_litter: { + keywords: [ "trash", "bin", "garbage", "circle" ], + char: "\ud83d\udeaf", + fitzpatrick_scale: false, + category: "symbols" + }, + no_bicycles: { + keywords: [ "cyclist", "prohibited", "circle" ], + char: "\ud83d\udeb3", + fitzpatrick_scale: false, + category: "symbols" + }, + "non-potable_water": { + keywords: [ "drink", "faucet", "tap", "circle" ], + char: "\ud83d\udeb1", + fitzpatrick_scale: false, + category: "symbols" + }, + underage: { + keywords: [ "18", "drink", "pub", "night", "minor", "circle" ], + char: "\ud83d\udd1e", + fitzpatrick_scale: false, + category: "symbols" + }, + no_mobile_phones: { + keywords: [ "iphone", "mute", "circle" ], + char: "\ud83d\udcf5", + fitzpatrick_scale: false, + category: "symbols" + }, + exclamation: { + keywords: [ "heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning" ], + char: "\u2757", + fitzpatrick_scale: false, + category: "symbols" + }, + grey_exclamation: { + keywords: [ "surprise", "punctuation", "gray", "wow", "warning" ], + char: "\u2755", + fitzpatrick_scale: false, + category: "symbols" + }, + question: { + keywords: [ "doubt", "confused" ], + char: "\u2753", + fitzpatrick_scale: false, + category: "symbols" + }, + grey_question: { + keywords: [ "doubts", "gray", "huh", "confused" ], + char: "\u2754", + fitzpatrick_scale: false, + category: "symbols" + }, + bangbang: { + keywords: [ "exclamation", "surprise" ], + char: "\u203c\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + interrobang: { + keywords: [ "wat", "punctuation", "surprise" ], + char: "\u2049\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + 100: { + keywords: [ "score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred" ], + char: "\ud83d\udcaf", + fitzpatrick_scale: false, + category: "symbols" + }, + low_brightness: { + keywords: [ "sun", "afternoon", "warm", "summer" ], + char: "\ud83d\udd05", + fitzpatrick_scale: false, + category: "symbols" + }, + high_brightness: { + keywords: [ "sun", "light" ], + char: "\ud83d\udd06", + fitzpatrick_scale: false, + category: "symbols" + }, + trident: { + keywords: [ "weapon", "spear" ], + char: "\ud83d\udd31", + fitzpatrick_scale: false, + category: "symbols" + }, + fleur_de_lis: { + keywords: [ "decorative", "scout" ], + char: "\u269c", + fitzpatrick_scale: false, + category: "symbols" + }, + part_alternation_mark: { + keywords: [ "graph", "presentation", "stats", "business", "economics", "bad" ], + char: "\u303d\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + warning: { + keywords: [ "exclamation", "wip", "alert", "error", "problem", "issue" ], + char: "\u26a0\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + children_crossing: { + keywords: [ "school", "warning", "danger", "sign", "driving", "yellow-diamond" ], + char: "\ud83d\udeb8", + fitzpatrick_scale: false, + category: "symbols" + }, + beginner: { + keywords: [ "badge", "shield" ], + char: "\ud83d\udd30", + fitzpatrick_scale: false, + category: "symbols" + }, + recycle: { + keywords: [ "arrow", "environment", "garbage", "trash" ], + char: "\u267b\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + u6307: { + keywords: [ "chinese", "point", "green-square", "kanji" ], + char: "\ud83c\ude2f", + fitzpatrick_scale: false, + category: "symbols" + }, + chart: { + keywords: [ "green-square", "graph", "presentation", "stats" ], + char: "\ud83d\udcb9", + fitzpatrick_scale: false, + category: "symbols" + }, + sparkle: { + keywords: [ "stars", "green-square", "awesome", "good", "fireworks" ], + char: "\u2747\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + eight_spoked_asterisk: { + keywords: [ "star", "sparkle", "green-square" ], + char: "\u2733\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + negative_squared_cross_mark: { + keywords: [ "x", "green-square", "no", "deny" ], + char: "\u274e", + fitzpatrick_scale: false, + category: "symbols" + }, + white_check_mark: { + keywords: [ "green-square", "ok", "agree", "vote", "election", "answer", "tick" ], + char: "\u2705", + fitzpatrick_scale: false, + category: "symbols" + }, + diamond_shape_with_a_dot_inside: { + keywords: [ "jewel", "blue", "gem", "crystal", "fancy" ], + char: "\ud83d\udca0", + fitzpatrick_scale: false, + category: "symbols" + }, + cyclone: { + keywords: [ "weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon" ], + char: "\ud83c\udf00", + fitzpatrick_scale: false, + category: "symbols" + }, + loop: { + keywords: [ "tape", "cassette" ], + char: "\u27bf", + fitzpatrick_scale: false, + category: "symbols" + }, + globe_with_meridians: { + keywords: [ "earth", "international", "world", "internet", "interweb", "i18n" ], + char: "\ud83c\udf10", + fitzpatrick_scale: false, + category: "symbols" + }, + m: { + keywords: [ "alphabet", "blue-circle", "letter" ], + char: "\u24c2\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + atm: { + keywords: [ "money", "sales", "cash", "blue-square", "payment", "bank" ], + char: "\ud83c\udfe7", + fitzpatrick_scale: false, + category: "symbols" + }, + sa: { + keywords: [ "japanese", "blue-square", "katakana" ], + char: "\ud83c\ude02\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + passport_control: { + keywords: [ "custom", "blue-square" ], + char: "\ud83d\udec2", + fitzpatrick_scale: false, + category: "symbols" + }, + customs: { + keywords: [ "passport", "border", "blue-square" ], + char: "\ud83d\udec3", + fitzpatrick_scale: false, + category: "symbols" + }, + baggage_claim: { + keywords: [ "blue-square", "airport", "transport" ], + char: "\ud83d\udec4", + fitzpatrick_scale: false, + category: "symbols" + }, + left_luggage: { + keywords: [ "blue-square", "travel" ], + char: "\ud83d\udec5", + fitzpatrick_scale: false, + category: "symbols" + }, + wheelchair: { + keywords: [ "blue-square", "disabled", "a11y", "accessibility" ], + char: "\u267f", + fitzpatrick_scale: false, + category: "symbols" + }, + no_smoking: { + keywords: [ "cigarette", "blue-square", "smell", "smoke" ], + char: "\ud83d\udead", + fitzpatrick_scale: false, + category: "symbols" + }, + wc: { + keywords: [ "toilet", "restroom", "blue-square" ], + char: "\ud83d\udebe", + fitzpatrick_scale: false, + category: "symbols" + }, + parking: { + keywords: [ "cars", "blue-square", "alphabet", "letter" ], + char: "\ud83c\udd7f\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + potable_water: { + keywords: [ "blue-square", "liquid", "restroom", "cleaning", "faucet" ], + char: "\ud83d\udeb0", + fitzpatrick_scale: false, + category: "symbols" + }, + mens: { + keywords: [ "toilet", "restroom", "wc", "blue-square", "gender", "male" ], + char: "\ud83d\udeb9", + fitzpatrick_scale: false, + category: "symbols" + }, + womens: { + keywords: [ "purple-square", "woman", "female", "toilet", "loo", "restroom", "gender" ], + char: "\ud83d\udeba", + fitzpatrick_scale: false, + category: "symbols" + }, + baby_symbol: { + keywords: [ "orange-square", "child" ], + char: "\ud83d\udebc", + fitzpatrick_scale: false, + category: "symbols" + }, + restroom: { + keywords: [ "blue-square", "toilet", "refresh", "wc", "gender" ], + char: "\ud83d\udebb", + fitzpatrick_scale: false, + category: "symbols" + }, + put_litter_in_its_place: { + keywords: [ "blue-square", "sign", "human", "info" ], + char: "\ud83d\udeae", + fitzpatrick_scale: false, + category: "symbols" + }, + cinema: { + keywords: [ "blue-square", "record", "film", "movie", "curtain", "stage", "theater" ], + char: "\ud83c\udfa6", + fitzpatrick_scale: false, + category: "symbols" + }, + signal_strength: { + keywords: [ "blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars" ], + char: "\ud83d\udcf6", + fitzpatrick_scale: false, + category: "symbols" + }, + koko: { + keywords: [ "blue-square", "here", "katakana", "japanese", "destination" ], + char: "\ud83c\ude01", + fitzpatrick_scale: false, + category: "symbols" + }, + ng: { + keywords: [ "blue-square", "words", "shape", "icon" ], + char: "\ud83c\udd96", + fitzpatrick_scale: false, + category: "symbols" + }, + ok: { + keywords: [ "good", "agree", "yes", "blue-square" ], + char: "\ud83c\udd97", + fitzpatrick_scale: false, + category: "symbols" + }, + up: { + keywords: [ "blue-square", "above", "high" ], + char: "\ud83c\udd99", + fitzpatrick_scale: false, + category: "symbols" + }, + cool: { + keywords: [ "words", "blue-square" ], + char: "\ud83c\udd92", + fitzpatrick_scale: false, + category: "symbols" + }, + new: { + keywords: [ "blue-square", "words", "start" ], + char: "\ud83c\udd95", + fitzpatrick_scale: false, + category: "symbols" + }, + free: { + keywords: [ "blue-square", "words" ], + char: "\ud83c\udd93", + fitzpatrick_scale: false, + category: "symbols" + }, + zero: { + keywords: [ "0", "numbers", "blue-square", "null" ], + char: "0\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + one: { + keywords: [ "blue-square", "numbers", "1" ], + char: "1\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + two: { + keywords: [ "numbers", "2", "prime", "blue-square" ], + char: "2\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + three: { + keywords: [ "3", "numbers", "prime", "blue-square" ], + char: "3\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + four: { + keywords: [ "4", "numbers", "blue-square" ], + char: "4\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + five: { + keywords: [ "5", "numbers", "blue-square", "prime" ], + char: "5\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + six: { + keywords: [ "6", "numbers", "blue-square" ], + char: "6\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + seven: { + keywords: [ "7", "numbers", "blue-square", "prime" ], + char: "7\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + eight: { + keywords: [ "8", "blue-square", "numbers" ], + char: "8\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + nine: { + keywords: [ "blue-square", "numbers", "9" ], + char: "9\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + keycap_ten: { + keywords: [ "numbers", "10", "blue-square" ], + char: "\ud83d\udd1f", + fitzpatrick_scale: false, + category: "symbols" + }, + asterisk: { + keywords: [ "star", "keycap" ], + char: "*\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + 1234: { + keywords: [ "numbers", "blue-square" ], + char: "\ud83d\udd22", + fitzpatrick_scale: false, + category: "symbols" + }, + eject_button: { + keywords: [ "blue-square" ], + char: "\u23cf\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_forward: { + keywords: [ "blue-square", "right", "direction", "play" ], + char: "\u25b6\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + pause_button: { + keywords: [ "pause", "blue-square" ], + char: "\u23f8", + fitzpatrick_scale: false, + category: "symbols" + }, + next_track_button: { + keywords: [ "forward", "next", "blue-square" ], + char: "\u23ed", + fitzpatrick_scale: false, + category: "symbols" + }, + stop_button: { + keywords: [ "blue-square" ], + char: "\u23f9", + fitzpatrick_scale: false, + category: "symbols" + }, + record_button: { + keywords: [ "blue-square" ], + char: "\u23fa", + fitzpatrick_scale: false, + category: "symbols" + }, + play_or_pause_button: { + keywords: [ "blue-square", "play", "pause" ], + char: "\u23ef", + fitzpatrick_scale: false, + category: "symbols" + }, + previous_track_button: { + keywords: [ "backward" ], + char: "\u23ee", + fitzpatrick_scale: false, + category: "symbols" + }, + fast_forward: { + keywords: [ "blue-square", "play", "speed", "continue" ], + char: "\u23e9", + fitzpatrick_scale: false, + category: "symbols" + }, + rewind: { + keywords: [ "play", "blue-square" ], + char: "\u23ea", + fitzpatrick_scale: false, + category: "symbols" + }, + twisted_rightwards_arrows: { + keywords: [ "blue-square", "shuffle", "music", "random" ], + char: "\ud83d\udd00", + fitzpatrick_scale: false, + category: "symbols" + }, + repeat: { + keywords: [ "loop", "record" ], + char: "\ud83d\udd01", + fitzpatrick_scale: false, + category: "symbols" + }, + repeat_one: { + keywords: [ "blue-square", "loop" ], + char: "\ud83d\udd02", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_backward: { + keywords: [ "blue-square", "left", "direction" ], + char: "\u25c0\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up_small: { + keywords: [ "blue-square", "triangle", "direction", "point", "forward", "top" ], + char: "\ud83d\udd3c", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_down_small: { + keywords: [ "blue-square", "direction", "bottom" ], + char: "\ud83d\udd3d", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_double_up: { + keywords: [ "blue-square", "direction", "top" ], + char: "\u23eb", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_double_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: "\u23ec", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_right: { + keywords: [ "blue-square", "next" ], + char: "\u27a1\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_left: { + keywords: [ "blue-square", "previous", "back" ], + char: "\u2b05\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up: { + keywords: [ "blue-square", "continue", "top", "direction" ], + char: "\u2b06\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: "\u2b07\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_upper_right: { + keywords: [ "blue-square", "point", "direction", "diagonal", "northeast" ], + char: "\u2197\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_lower_right: { + keywords: [ "blue-square", "direction", "diagonal", "southeast" ], + char: "\u2198\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_lower_left: { + keywords: [ "blue-square", "direction", "diagonal", "southwest" ], + char: "\u2199\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_upper_left: { + keywords: [ "blue-square", "point", "direction", "diagonal", "northwest" ], + char: "\u2196\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_up_down: { + keywords: [ "blue-square", "direction", "way", "vertical" ], + char: "\u2195\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + left_right_arrow: { + keywords: [ "shape", "direction", "horizontal", "sideways" ], + char: "\u2194\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrows_counterclockwise: { + keywords: [ "blue-square", "sync", "cycle" ], + char: "\ud83d\udd04", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_right_hook: { + keywords: [ "blue-square", "return", "rotate", "direction" ], + char: "\u21aa\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + leftwards_arrow_with_hook: { + keywords: [ "back", "return", "blue-square", "undo", "enter" ], + char: "\u21a9\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_heading_up: { + keywords: [ "blue-square", "direction", "top" ], + char: "\u2934\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrow_heading_down: { + keywords: [ "blue-square", "direction", "bottom" ], + char: "\u2935\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + hash: { + keywords: [ "symbol", "blue-square", "twitter" ], + char: "#\ufe0f\u20e3", + fitzpatrick_scale: false, + category: "symbols" + }, + information_source: { + keywords: [ "blue-square", "alphabet", "letter" ], + char: "\u2139\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + abc: { + keywords: [ "blue-square", "alphabet" ], + char: "\ud83d\udd24", + fitzpatrick_scale: false, + category: "symbols" + }, + abcd: { + keywords: [ "blue-square", "alphabet" ], + char: "\ud83d\udd21", + fitzpatrick_scale: false, + category: "symbols" + }, + capital_abcd: { + keywords: [ "alphabet", "words", "blue-square" ], + char: "\ud83d\udd20", + fitzpatrick_scale: false, + category: "symbols" + }, + symbols: { + keywords: [ "blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters" ], + char: "\ud83d\udd23", + fitzpatrick_scale: false, + category: "symbols" + }, + musical_note: { + keywords: [ "score", "tone", "sound" ], + char: "\ud83c\udfb5", + fitzpatrick_scale: false, + category: "symbols" + }, + notes: { + keywords: [ "music", "score" ], + char: "\ud83c\udfb6", + fitzpatrick_scale: false, + category: "symbols" + }, + wavy_dash: { + keywords: [ "draw", "line", "moustache", "mustache", "squiggle", "scribble" ], + char: "\u3030\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + curly_loop: { + keywords: [ "scribble", "draw", "shape", "squiggle" ], + char: "\u27b0", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_check_mark: { + keywords: [ "ok", "nike", "answer", "yes", "tick" ], + char: "\u2714\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + arrows_clockwise: { + keywords: [ "sync", "cycle", "round", "repeat" ], + char: "\ud83d\udd03", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_plus_sign: { + keywords: [ "math", "calculation", "addition", "more", "increase" ], + char: "\u2795", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_minus_sign: { + keywords: [ "math", "calculation", "subtract", "less" ], + char: "\u2796", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_division_sign: { + keywords: [ "divide", "math", "calculation" ], + char: "\u2797", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_multiplication_x: { + keywords: [ "math", "calculation" ], + char: "\u2716\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + infinity: { + keywords: [ "forever" ], + char: "\u267e", + fitzpatrick_scale: false, + category: "symbols" + }, + heavy_dollar_sign: { + keywords: [ "money", "sales", "payment", "currency", "buck" ], + char: "\ud83d\udcb2", + fitzpatrick_scale: false, + category: "symbols" + }, + currency_exchange: { + keywords: [ "money", "sales", "dollar", "travel" ], + char: "\ud83d\udcb1", + fitzpatrick_scale: false, + category: "symbols" + }, + copyright: { + keywords: [ "ip", "license", "circle", "law", "legal" ], + char: "\xa9\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + registered: { + keywords: [ "alphabet", "circle" ], + char: "\xae\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + tm: { + keywords: [ "trademark", "brand", "law", "legal" ], + char: "\u2122\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + end: { + keywords: [ "words", "arrow" ], + char: "\ud83d\udd1a", + fitzpatrick_scale: false, + category: "symbols" + }, + back: { + keywords: [ "arrow", "words", "return" ], + char: "\ud83d\udd19", + fitzpatrick_scale: false, + category: "symbols" + }, + on: { + keywords: [ "arrow", "words" ], + char: "\ud83d\udd1b", + fitzpatrick_scale: false, + category: "symbols" + }, + top: { + keywords: [ "words", "blue-square" ], + char: "\ud83d\udd1d", + fitzpatrick_scale: false, + category: "symbols" + }, + soon: { + keywords: [ "arrow", "words" ], + char: "\ud83d\udd1c", + fitzpatrick_scale: false, + category: "symbols" + }, + ballot_box_with_check: { + keywords: [ "ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick" ], + char: "\u2611\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + radio_button: { + keywords: [ "input", "old", "music", "circle" ], + char: "\ud83d\udd18", + fitzpatrick_scale: false, + category: "symbols" + }, + white_circle: { + keywords: [ "shape", "round" ], + char: "\u26aa", + fitzpatrick_scale: false, + category: "symbols" + }, + black_circle: { + keywords: [ "shape", "button", "round" ], + char: "\u26ab", + fitzpatrick_scale: false, + category: "symbols" + }, + red_circle: { + keywords: [ "shape", "error", "danger" ], + char: "\ud83d\udd34", + fitzpatrick_scale: false, + category: "symbols" + }, + large_blue_circle: { + keywords: [ "shape", "icon", "button" ], + char: "\ud83d\udd35", + fitzpatrick_scale: false, + category: "symbols" + }, + small_orange_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: "\ud83d\udd38", + fitzpatrick_scale: false, + category: "symbols" + }, + small_blue_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: "\ud83d\udd39", + fitzpatrick_scale: false, + category: "symbols" + }, + large_orange_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: "\ud83d\udd36", + fitzpatrick_scale: false, + category: "symbols" + }, + large_blue_diamond: { + keywords: [ "shape", "jewel", "gem" ], + char: "\ud83d\udd37", + fitzpatrick_scale: false, + category: "symbols" + }, + small_red_triangle: { + keywords: [ "shape", "direction", "up", "top" ], + char: "\ud83d\udd3a", + fitzpatrick_scale: false, + category: "symbols" + }, + black_small_square: { + keywords: [ "shape", "icon" ], + char: "\u25aa\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + white_small_square: { + keywords: [ "shape", "icon" ], + char: "\u25ab\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + black_large_square: { + keywords: [ "shape", "icon", "button" ], + char: "\u2b1b", + fitzpatrick_scale: false, + category: "symbols" + }, + white_large_square: { + keywords: [ "shape", "icon", "stone", "button" ], + char: "\u2b1c", + fitzpatrick_scale: false, + category: "symbols" + }, + small_red_triangle_down: { + keywords: [ "shape", "direction", "bottom" ], + char: "\ud83d\udd3b", + fitzpatrick_scale: false, + category: "symbols" + }, + black_medium_square: { + keywords: [ "shape", "button", "icon" ], + char: "\u25fc\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + white_medium_square: { + keywords: [ "shape", "stone", "icon" ], + char: "\u25fb\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + black_medium_small_square: { + keywords: [ "icon", "shape", "button" ], + char: "\u25fe", + fitzpatrick_scale: false, + category: "symbols" + }, + white_medium_small_square: { + keywords: [ "shape", "stone", "icon", "button" ], + char: "\u25fd", + fitzpatrick_scale: false, + category: "symbols" + }, + black_square_button: { + keywords: [ "shape", "input", "frame" ], + char: "\ud83d\udd32", + fitzpatrick_scale: false, + category: "symbols" + }, + white_square_button: { + keywords: [ "shape", "input" ], + char: "\ud83d\udd33", + fitzpatrick_scale: false, + category: "symbols" + }, + speaker: { + keywords: [ "sound", "volume", "silence", "broadcast" ], + char: "\ud83d\udd08", + fitzpatrick_scale: false, + category: "symbols" + }, + sound: { + keywords: [ "volume", "speaker", "broadcast" ], + char: "\ud83d\udd09", + fitzpatrick_scale: false, + category: "symbols" + }, + loud_sound: { + keywords: [ "volume", "noise", "noisy", "speaker", "broadcast" ], + char: "\ud83d\udd0a", + fitzpatrick_scale: false, + category: "symbols" + }, + mute: { + keywords: [ "sound", "volume", "silence", "quiet" ], + char: "\ud83d\udd07", + fitzpatrick_scale: false, + category: "symbols" + }, + mega: { + keywords: [ "sound", "speaker", "volume" ], + char: "\ud83d\udce3", + fitzpatrick_scale: false, + category: "symbols" + }, + loudspeaker: { + keywords: [ "volume", "sound" ], + char: "\ud83d\udce2", + fitzpatrick_scale: false, + category: "symbols" + }, + bell: { + keywords: [ "sound", "notification", "christmas", "xmas", "chime" ], + char: "\ud83d\udd14", + fitzpatrick_scale: false, + category: "symbols" + }, + no_bell: { + keywords: [ "sound", "volume", "mute", "quiet", "silent" ], + char: "\ud83d\udd15", + fitzpatrick_scale: false, + category: "symbols" + }, + black_joker: { + keywords: [ "poker", "cards", "game", "play", "magic" ], + char: "\ud83c\udccf", + fitzpatrick_scale: false, + category: "symbols" + }, + mahjong: { + keywords: [ "game", "play", "chinese", "kanji" ], + char: "\ud83c\udc04", + fitzpatrick_scale: false, + category: "symbols" + }, + spades: { + keywords: [ "poker", "cards", "suits", "magic" ], + char: "\u2660\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + clubs: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: "\u2663\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + hearts: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: "\u2665\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + diamonds: { + keywords: [ "poker", "cards", "magic", "suits" ], + char: "\u2666\ufe0f", + fitzpatrick_scale: false, + category: "symbols" + }, + flower_playing_cards: { + keywords: [ "game", "sunset", "red" ], + char: "\ud83c\udfb4", + fitzpatrick_scale: false, + category: "symbols" + }, + thought_balloon: { + keywords: [ "bubble", "cloud", "speech", "thinking", "dream" ], + char: "\ud83d\udcad", + fitzpatrick_scale: false, + category: "symbols" + }, + right_anger_bubble: { + keywords: [ "caption", "speech", "thinking", "mad" ], + char: "\ud83d\uddef", + fitzpatrick_scale: false, + category: "symbols" + }, + speech_balloon: { + keywords: [ "bubble", "words", "message", "talk", "chatting" ], + char: "\ud83d\udcac", + fitzpatrick_scale: false, + category: "symbols" + }, + left_speech_bubble: { + keywords: [ "words", "message", "talk", "chatting" ], + char: "\ud83d\udde8", + fitzpatrick_scale: false, + category: "symbols" + }, + clock1: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd50", + fitzpatrick_scale: false, + category: "symbols" + }, + clock2: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd51", + fitzpatrick_scale: false, + category: "symbols" + }, + clock3: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd52", + fitzpatrick_scale: false, + category: "symbols" + }, + clock4: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd53", + fitzpatrick_scale: false, + category: "symbols" + }, + clock5: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd54", + fitzpatrick_scale: false, + category: "symbols" + }, + clock6: { + keywords: [ "time", "late", "early", "schedule", "dawn", "dusk" ], + char: "\ud83d\udd55", + fitzpatrick_scale: false, + category: "symbols" + }, + clock7: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd56", + fitzpatrick_scale: false, + category: "symbols" + }, + clock8: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd57", + fitzpatrick_scale: false, + category: "symbols" + }, + clock9: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd58", + fitzpatrick_scale: false, + category: "symbols" + }, + clock10: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd59", + fitzpatrick_scale: false, + category: "symbols" + }, + clock11: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd5a", + fitzpatrick_scale: false, + category: "symbols" + }, + clock12: { + keywords: [ "time", "noon", "midnight", "midday", "late", "early", "schedule" ], + char: "\ud83d\udd5b", + fitzpatrick_scale: false, + category: "symbols" + }, + clock130: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd5c", + fitzpatrick_scale: false, + category: "symbols" + }, + clock230: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd5d", + fitzpatrick_scale: false, + category: "symbols" + }, + clock330: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd5e", + fitzpatrick_scale: false, + category: "symbols" + }, + clock430: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd5f", + fitzpatrick_scale: false, + category: "symbols" + }, + clock530: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd60", + fitzpatrick_scale: false, + category: "symbols" + }, + clock630: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd61", + fitzpatrick_scale: false, + category: "symbols" + }, + clock730: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd62", + fitzpatrick_scale: false, + category: "symbols" + }, + clock830: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd63", + fitzpatrick_scale: false, + category: "symbols" + }, + clock930: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd64", + fitzpatrick_scale: false, + category: "symbols" + }, + clock1030: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd65", + fitzpatrick_scale: false, + category: "symbols" + }, + clock1130: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd66", + fitzpatrick_scale: false, + category: "symbols" + }, + clock1230: { + keywords: [ "time", "late", "early", "schedule" ], + char: "\ud83d\udd67", + fitzpatrick_scale: false, + category: "symbols" + }, + afghanistan: { + keywords: [ "af", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + aland_islands: { + keywords: [ "\xc5land", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddfd", + fitzpatrick_scale: false, + category: "flags" + }, + albania: { + keywords: [ "al", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + algeria: { + keywords: [ "dz", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + american_samoa: { + keywords: [ "american", "ws", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + andorra: { + keywords: [ "ad", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + angola: { + keywords: [ "ao", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + anguilla: { + keywords: [ "ai", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + antarctica: { + keywords: [ "aq", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf6", + fitzpatrick_scale: false, + category: "flags" + }, + antigua_barbuda: { + keywords: [ "antigua", "barbuda", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + argentina: { + keywords: [ "ar", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + armenia: { + keywords: [ "am", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + aruba: { + keywords: [ "aw", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + australia: { + keywords: [ "au", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + austria: { + keywords: [ "at", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + azerbaijan: { + keywords: [ "az", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + bahamas: { + keywords: [ "bs", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + bahrain: { + keywords: [ "bh", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + bangladesh: { + keywords: [ "bd", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + barbados: { + keywords: [ "bb", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\udde7", + fitzpatrick_scale: false, + category: "flags" + }, + belarus: { + keywords: [ "by", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + belgium: { + keywords: [ "be", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + belize: { + keywords: [ "bz", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + benin: { + keywords: [ "bj", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddef", + fitzpatrick_scale: false, + category: "flags" + }, + bermuda: { + keywords: [ "bm", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + bhutan: { + keywords: [ "bt", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + bolivia: { + keywords: [ "bo", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + caribbean_netherlands: { + keywords: [ "bonaire", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf6", + fitzpatrick_scale: false, + category: "flags" + }, + bosnia_herzegovina: { + keywords: [ "bosnia", "herzegovina", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + botswana: { + keywords: [ "bw", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + brazil: { + keywords: [ "br", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + british_indian_ocean_territory: { + keywords: [ "british", "indian", "ocean", "territory", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + british_virgin_islands: { + keywords: [ "british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + brunei: { + keywords: [ "bn", "darussalam", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + bulgaria: { + keywords: [ "bg", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + burkina_faso: { + keywords: [ "burkina", "faso", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + burundi: { + keywords: [ "bi", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + cape_verde: { + keywords: [ "cabo", "verde", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddfb", + fitzpatrick_scale: false, + category: "flags" + }, + cambodia: { + keywords: [ "kh", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + cameroon: { + keywords: [ "cm", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + canada: { + keywords: [ "ca", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + canary_islands: { + keywords: [ "canary", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + cayman_islands: { + keywords: [ "cayman", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + central_african_republic: { + keywords: [ "central", "african", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + chad: { + keywords: [ "td", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + chile: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + cn: { + keywords: [ "china", "chinese", "prc", "flag", "country", "nation", "banner" ], + char: "\ud83c\udde8\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + christmas_island: { + keywords: [ "christmas", "island", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddfd", + fitzpatrick_scale: false, + category: "flags" + }, + cocos_islands: { + keywords: [ "cocos", "keeling", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + colombia: { + keywords: [ "co", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + comoros: { + keywords: [ "km", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + congo_brazzaville: { + keywords: [ "congo", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + congo_kinshasa: { + keywords: [ "congo", "democratic", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + cook_islands: { + keywords: [ "cook", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + costa_rica: { + keywords: [ "costa", "rica", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + croatia: { + keywords: [ "hr", "flag", "nation", "country", "banner" ], + char: "\ud83c\udded\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + cuba: { + keywords: [ "cu", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + curacao: { + keywords: [ "cura\xe7ao", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + cyprus: { + keywords: [ "cy", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + czech_republic: { + keywords: [ "cz", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + denmark: { + keywords: [ "dk", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + djibouti: { + keywords: [ "dj", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddef", + fitzpatrick_scale: false, + category: "flags" + }, + dominica: { + keywords: [ "dm", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + dominican_republic: { + keywords: [ "dominican", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + ecuador: { + keywords: [ "ec", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + egypt: { + keywords: [ "eg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + el_salvador: { + keywords: [ "el", "salvador", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddfb", + fitzpatrick_scale: false, + category: "flags" + }, + equatorial_guinea: { + keywords: [ "equatorial", "gn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf6", + fitzpatrick_scale: false, + category: "flags" + }, + eritrea: { + keywords: [ "er", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + estonia: { + keywords: [ "ee", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + ethiopia: { + keywords: [ "et", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + eu: { + keywords: [ "european", "union", "flag", "banner" ], + char: "\ud83c\uddea\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + falkland_islands: { + keywords: [ "falkland", "islands", "malvinas", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddeb\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + faroe_islands: { + keywords: [ "faroe", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddeb\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + fiji: { + keywords: [ "fj", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddeb\ud83c\uddef", + fitzpatrick_scale: false, + category: "flags" + }, + finland: { + keywords: [ "fi", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddeb\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + fr: { + keywords: [ "banner", "flag", "nation", "france", "french", "country" ], + char: "\ud83c\uddeb\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + french_guiana: { + keywords: [ "french", "guiana", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + french_polynesia: { + keywords: [ "french", "polynesia", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + french_southern_territories: { + keywords: [ "french", "southern", "territories", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + gabon: { + keywords: [ "ga", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + gambia: { + keywords: [ "gm", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + georgia: { + keywords: [ "ge", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + de: { + keywords: [ "german", "nation", "flag", "country", "banner" ], + char: "\ud83c\udde9\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + ghana: { + keywords: [ "gh", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + gibraltar: { + keywords: [ "gi", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + greece: { + keywords: [ "gr", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + greenland: { + keywords: [ "gl", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + grenada: { + keywords: [ "gd", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + guadeloupe: { + keywords: [ "gp", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf5", + fitzpatrick_scale: false, + category: "flags" + }, + guam: { + keywords: [ "gu", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + guatemala: { + keywords: [ "gt", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + guernsey: { + keywords: [ "gg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + guinea: { + keywords: [ "gn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + guinea_bissau: { + keywords: [ "gw", "bissau", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + guyana: { + keywords: [ "gy", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + haiti: { + keywords: [ "ht", "flag", "nation", "country", "banner" ], + char: "\ud83c\udded\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + honduras: { + keywords: [ "hn", "flag", "nation", "country", "banner" ], + char: "\ud83c\udded\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + hong_kong: { + keywords: [ "hong", "kong", "flag", "nation", "country", "banner" ], + char: "\ud83c\udded\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + hungary: { + keywords: [ "hu", "flag", "nation", "country", "banner" ], + char: "\ud83c\udded\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + iceland: { + keywords: [ "is", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + india: { + keywords: [ "in", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + indonesia: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + iran: { + keywords: [ "iran,", "islamic", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + iraq: { + keywords: [ "iq", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf6", + fitzpatrick_scale: false, + category: "flags" + }, + ireland: { + keywords: [ "ie", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + isle_of_man: { + keywords: [ "isle", "man", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + israel: { + keywords: [ "il", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + it: { + keywords: [ "italy", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddee\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + cote_divoire: { + keywords: [ "ivory", "coast", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + jamaica: { + keywords: [ "jm", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddef\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + jp: { + keywords: [ "japanese", "nation", "flag", "country", "banner" ], + char: "\ud83c\uddef\ud83c\uddf5", + fitzpatrick_scale: false, + category: "flags" + }, + jersey: { + keywords: [ "je", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddef\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + jordan: { + keywords: [ "jo", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddef\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + kazakhstan: { + keywords: [ "kz", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + kenya: { + keywords: [ "ke", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + kiribati: { + keywords: [ "ki", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + kosovo: { + keywords: [ "xk", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfd\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + kuwait: { + keywords: [ "kw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + kyrgyzstan: { + keywords: [ "kg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + laos: { + keywords: [ "lao", "democratic", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + latvia: { + keywords: [ "lv", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddfb", + fitzpatrick_scale: false, + category: "flags" + }, + lebanon: { + keywords: [ "lb", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\udde7", + fitzpatrick_scale: false, + category: "flags" + }, + lesotho: { + keywords: [ "ls", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + liberia: { + keywords: [ "lr", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + libya: { + keywords: [ "ly", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + liechtenstein: { + keywords: [ "li", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + lithuania: { + keywords: [ "lt", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + luxembourg: { + keywords: [ "lu", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + macau: { + keywords: [ "macao", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + macedonia: { + keywords: [ "macedonia,", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + madagascar: { + keywords: [ "mg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + malawi: { + keywords: [ "mw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + malaysia: { + keywords: [ "my", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + maldives: { + keywords: [ "mv", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddfb", + fitzpatrick_scale: false, + category: "flags" + }, + mali: { + keywords: [ "ml", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + malta: { + keywords: [ "mt", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + marshall_islands: { + keywords: [ "marshall", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + martinique: { + keywords: [ "mq", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf6", + fitzpatrick_scale: false, + category: "flags" + }, + mauritania: { + keywords: [ "mr", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + mauritius: { + keywords: [ "mu", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + mayotte: { + keywords: [ "yt", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfe\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + mexico: { + keywords: [ "mx", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddfd", + fitzpatrick_scale: false, + category: "flags" + }, + micronesia: { + keywords: [ "micronesia,", "federated", "states", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddeb\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + moldova: { + keywords: [ "moldova,", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + monaco: { + keywords: [ "mc", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + mongolia: { + keywords: [ "mn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + montenegro: { + keywords: [ "me", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + montserrat: { + keywords: [ "ms", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + morocco: { + keywords: [ "ma", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + mozambique: { + keywords: [ "mz", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + myanmar: { + keywords: [ "mm", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + namibia: { + keywords: [ "na", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + nauru: { + keywords: [ "nr", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + nepal: { + keywords: [ "np", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddf5", + fitzpatrick_scale: false, + category: "flags" + }, + netherlands: { + keywords: [ "nl", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + new_caledonia: { + keywords: [ "new", "caledonia", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + new_zealand: { + keywords: [ "new", "zealand", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + nicaragua: { + keywords: [ "ni", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + niger: { + keywords: [ "ne", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + nigeria: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + niue: { + keywords: [ "nu", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + norfolk_island: { + keywords: [ "norfolk", "island", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + northern_mariana_islands: { + keywords: [ "northern", "mariana", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf2\ud83c\uddf5", + fitzpatrick_scale: false, + category: "flags" + }, + north_korea: { + keywords: [ "north", "korea", "nation", "flag", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddf5", + fitzpatrick_scale: false, + category: "flags" + }, + norway: { + keywords: [ "no", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf3\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + oman: { + keywords: [ "om_symbol", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf4\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + pakistan: { + keywords: [ "pk", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + palau: { + keywords: [ "pw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + palestinian_territories: { + keywords: [ "palestine", "palestinian", "territories", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + panama: { + keywords: [ "pa", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + papua_new_guinea: { + keywords: [ "papua", "new", "guinea", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + paraguay: { + keywords: [ "py", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + peru: { + keywords: [ "pe", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + philippines: { + keywords: [ "ph", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + pitcairn_islands: { + keywords: [ "pitcairn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + poland: { + keywords: [ "pl", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + portugal: { + keywords: [ "pt", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + puerto_rico: { + keywords: [ "puerto", "rico", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + qatar: { + keywords: [ "qa", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf6\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + reunion: { + keywords: [ "r\xe9union", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf7\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + romania: { + keywords: [ "ro", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf7\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + ru: { + keywords: [ "russian", "federation", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf7\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + rwanda: { + keywords: [ "rw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf7\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + st_barthelemy: { + keywords: [ "saint", "barth\xe9lemy", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde7\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + st_helena: { + keywords: [ "saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + st_kitts_nevis: { + keywords: [ "saint", "kitts", "nevis", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + st_lucia: { + keywords: [ "saint", "lucia", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + st_pierre_miquelon: { + keywords: [ "saint", "pierre", "miquelon", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf5\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + st_vincent_grenadines: { + keywords: [ "saint", "vincent", "grenadines", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + samoa: { + keywords: [ "ws", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfc\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + san_marino: { + keywords: [ "san", "marino", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + sao_tome_principe: { + keywords: [ "sao", "tome", "principe", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + saudi_arabia: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + senegal: { + keywords: [ "sn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + serbia: { + keywords: [ "rs", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf7\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + seychelles: { + keywords: [ "sc", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + sierra_leone: { + keywords: [ "sierra", "leone", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + singapore: { + keywords: [ "sg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + sint_maarten: { + keywords: [ "sint", "maarten", "dutch", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddfd", + fitzpatrick_scale: false, + category: "flags" + }, + slovakia: { + keywords: [ "sk", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + slovenia: { + keywords: [ "si", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + solomon_islands: { + keywords: [ "solomon", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\udde7", + fitzpatrick_scale: false, + category: "flags" + }, + somalia: { + keywords: [ "so", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + south_africa: { + keywords: [ "south", "africa", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddff\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + south_georgia_south_sandwich_islands: { + keywords: [ "south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddec\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + kr: { + keywords: [ "south", "korea", "nation", "flag", "country", "banner" ], + char: "\ud83c\uddf0\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + south_sudan: { + keywords: [ "south", "sd", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + es: { + keywords: [ "spain", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + sri_lanka: { + keywords: [ "sri", "lanka", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf1\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + sudan: { + keywords: [ "sd", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\udde9", + fitzpatrick_scale: false, + category: "flags" + }, + suriname: { + keywords: [ "sr", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + swaziland: { + keywords: [ "sz", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + sweden: { + keywords: [ "se", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + switzerland: { + keywords: [ "ch", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde8\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + syria: { + keywords: [ "syrian", "arab", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf8\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + taiwan: { + keywords: [ "tw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + tajikistan: { + keywords: [ "tj", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddef", + fitzpatrick_scale: false, + category: "flags" + }, + tanzania: { + keywords: [ "tanzania,", "united", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + thailand: { + keywords: [ "th", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + timor_leste: { + keywords: [ "timor", "leste", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf1", + fitzpatrick_scale: false, + category: "flags" + }, + togo: { + keywords: [ "tg", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + tokelau: { + keywords: [ "tk", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf0", + fitzpatrick_scale: false, + category: "flags" + }, + tonga: { + keywords: [ "to", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf4", + fitzpatrick_scale: false, + category: "flags" + }, + trinidad_tobago: { + keywords: [ "trinidad", "tobago", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf9", + fitzpatrick_scale: false, + category: "flags" + }, + tunisia: { + keywords: [ "tn", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + tr: { + keywords: [ "turkey", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf7", + fitzpatrick_scale: false, + category: "flags" + }, + turkmenistan: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + turks_caicos_islands: { + keywords: [ "turks", "caicos", "islands", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\udde8", + fitzpatrick_scale: false, + category: "flags" + }, + tuvalu: { + keywords: [ "flag", "nation", "country", "banner" ], + char: "\ud83c\uddf9\ud83c\uddfb", + fitzpatrick_scale: false, + category: "flags" + }, + uganda: { + keywords: [ "ug", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfa\ud83c\uddec", + fitzpatrick_scale: false, + category: "flags" + }, + ukraine: { + keywords: [ "ua", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfa\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + united_arab_emirates: { + keywords: [ "united", "arab", "emirates", "flag", "nation", "country", "banner" ], + char: "\ud83c\udde6\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + uk: { + keywords: [ "united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack" ], + char: "\ud83c\uddec\ud83c\udde7", + fitzpatrick_scale: false, + category: "flags" + }, + england: { + keywords: [ "flag", "english" ], + char: "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", + fitzpatrick_scale: false, + category: "flags" + }, + scotland: { + keywords: [ "flag", "scottish" ], + char: "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f", + fitzpatrick_scale: false, + category: "flags" + }, + wales: { + keywords: [ "flag", "welsh" ], + char: "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f", + fitzpatrick_scale: false, + category: "flags" + }, + us: { + keywords: [ "united", "states", "america", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfa\ud83c\uddf8", + fitzpatrick_scale: false, + category: "flags" + }, + us_virgin_islands: { + keywords: [ "virgin", "islands", "us", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\uddee", + fitzpatrick_scale: false, + category: "flags" + }, + uruguay: { + keywords: [ "uy", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfa\ud83c\uddfe", + fitzpatrick_scale: false, + category: "flags" + }, + uzbekistan: { + keywords: [ "uz", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfa\ud83c\uddff", + fitzpatrick_scale: false, + category: "flags" + }, + vanuatu: { + keywords: [ "vu", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\uddfa", + fitzpatrick_scale: false, + category: "flags" + }, + vatican_city: { + keywords: [ "vatican", "city", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\udde6", + fitzpatrick_scale: false, + category: "flags" + }, + venezuela: { + keywords: [ "ve", "bolivarian", "republic", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + vietnam: { + keywords: [ "viet", "nam", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfb\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + wallis_futuna: { + keywords: [ "wallis", "futuna", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfc\ud83c\uddeb", + fitzpatrick_scale: false, + category: "flags" + }, + western_sahara: { + keywords: [ "western", "sahara", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddea\ud83c\udded", + fitzpatrick_scale: false, + category: "flags" + }, + yemen: { + keywords: [ "ye", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddfe\ud83c\uddea", + fitzpatrick_scale: false, + category: "flags" + }, + zambia: { + keywords: [ "zm", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddff\ud83c\uddf2", + fitzpatrick_scale: false, + category: "flags" + }, + zimbabwe: { + keywords: [ "zw", "flag", "nation", "country", "banner" ], + char: "\ud83c\uddff\ud83c\uddfc", + fitzpatrick_scale: false, + category: "flags" + }, + united_nations: { + keywords: [ "un", "flag", "banner" ], + char: "\ud83c\uddfa\ud83c\uddf3", + fitzpatrick_scale: false, + category: "flags" + }, + pirate_flag: { + keywords: [ "skull", "crossbones", "flag", "banner" ], + char: "\ud83c\udff4\u200d\u2620\ufe0f", + fitzpatrick_scale: false, + category: "flags" + } +}); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/emoticons/js/emojis.min.js b/apps/st/public/tinymce/plugins/emoticons/js/emojis.min.js new file mode 100644 index 000000000..42cea9ab4 --- /dev/null +++ b/apps/st/public/tinymce/plugins/emoticons/js/emojis.min.js @@ -0,0 +1,2 @@ +// Source: npm package: emojilib, file:emojis.json +window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"\ud83d\ude00",fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"\ud83d\ude2c",fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"\ud83d\ude01",fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"\ud83d\ude02",fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"\ud83e\udd23",fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"\ud83e\udd73",fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"\ud83d\ude03",fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"\ud83d\ude04",fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"\ud83d\ude05",fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"\ud83d\ude06",fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"\ud83d\ude07",fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"\ud83d\ude09",fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"\ud83d\ude0a",fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"\ud83d\ude42",fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"\ud83d\ude43",fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"\u263a\ufe0f",fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"\ud83d\ude0b",fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"\ud83d\ude0c",fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"\ud83d\ude0d",fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"\ud83e\udd70",fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\ud83d\ude18",fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"\ud83d\ude17",fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"\ud83d\ude19",fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\ud83d\ude1a",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"\ud83d\ude1c",fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"\ud83e\udd2a",fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"\ud83e\udd28",fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"\ud83e\uddd0",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"\ud83d\ude1d",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"\ud83d\ude1b",fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"\ud83e\udd11",fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"\ud83e\udd13",fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"\ud83d\ude0e",fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"\ud83e\udd29",fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:"\ud83e\udd21",fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"\ud83e\udd20",fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:"\ud83e\udd17",fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"\ud83d\ude0f",fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"\ud83d\ude36",fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"\ud83d\ude10",fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"\ud83d\ude11",fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"\ud83d\ude12",fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"\ud83d\ude44",fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"\ud83e\udd14",fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"\ud83e\udd25",fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"\ud83e\udd2d",fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"\ud83e\udd2b",fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"\ud83e\udd2c",fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"\ud83e\udd2f",fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"\ud83d\ude33",fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"\ud83d\ude1e",fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"\ud83d\ude1f",fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"\ud83d\ude20",fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"\ud83d\ude21",fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"\ud83d\ude14",fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"\ud83d\ude15",fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"\ud83d\ude41",fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"\u2639",fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"\ud83d\ude23",fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"\ud83d\ude16",fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"\ud83d\ude2b",fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"\ud83d\ude29",fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"\ud83e\udd7a",fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"\ud83d\ude24",fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"\ud83d\ude2e",fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"\ud83d\ude31",fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"\ud83d\ude28",fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"\ud83d\ude30",fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:"\ud83d\ude2f",fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:"\ud83d\ude26",fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"\ud83d\ude27",fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"\ud83d\ude22",fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"\ud83d\ude25",fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:"\ud83e\udd24",fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"\ud83d\ude2a",fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"\ud83d\ude13",fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"\ud83e\udd75",fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"\ud83e\udd76",fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"\ud83d\ude2d",fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"\ud83d\ude35",fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"\ud83d\ude32",fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"\ud83e\udd10",fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"\ud83e\udd22",fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"\ud83e\udd27",fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:"\ud83e\udd2e",fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"\ud83d\ude37",fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"\ud83e\udd12",fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"\ud83e\udd15",fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"\ud83e\udd74",fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"\ud83d\ude34",fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"\ud83d\udca4",fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"\ud83d\udca9",fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"\ud83d\ude08",fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:"\ud83d\udc7f",fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"\ud83d\udc79",fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"\ud83d\udc7a",fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"\ud83d\udc80",fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"\ud83d\udc7b",fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"\ud83d\udc7d",fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:"\ud83e\udd16",fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"\ud83d\ude3a",fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"\ud83d\ude38",fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"\ud83d\ude39",fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"\ud83d\ude3b",fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"\ud83d\ude3c",fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"\ud83d\ude3d",fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"\ud83d\ude40",fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"\ud83d\ude3f",fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"\ud83d\ude3e",fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"\ud83e\udd32",fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"\ud83d\ude4c",fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"\ud83d\udc4f",fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"\ud83d\udc4b",fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"\ud83e\udd19",fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"\ud83d\udc4d",fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"\ud83d\udc4e",fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"\ud83d\udc4a",fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"\u270a",fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"\ud83e\udd1b",fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"\ud83e\udd1c",fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"\u270c",fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"\ud83d\udc4c",fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"\u270b",fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"\ud83e\udd1a",fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"\ud83d\udc50",fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"\ud83d\udcaa",fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"\ud83d\ude4f",fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:"\ud83e\uddb6",fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:"\ud83e\uddb5",fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:"\ud83e\udd1d",fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"\u261d",fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"\ud83d\udc46",fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"\ud83d\udc47",fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"\ud83d\udc48",fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"\ud83d\udc49",fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"\ud83d\udd95",fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"\ud83d\udd90",fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"\ud83e\udd1f",fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"\ud83e\udd18",fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"\ud83e\udd1e",fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"\ud83d\udd96",fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"\u270d",fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:"\ud83e\udd33",fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"\ud83d\udc85",fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:"\ud83d\udc44",fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:"\ud83e\uddb7",fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:"\ud83d\udc45",fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"\ud83d\udc42",fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:"\ud83d\udc43",fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"\ud83d\udc41",fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"\ud83d\udc40",fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:"\ud83e\udde0",fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"\ud83d\udc64",fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"\ud83d\udc65",fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"\ud83d\udde3",fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"\ud83d\udc76",fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:"\ud83e\uddd2",fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"\ud83d\udc66",fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:"\ud83d\udc67",fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:"\ud83e\uddd1",fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"\ud83d\udc68",fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:"\ud83d\udc69",fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"\ud83d\udc71\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"\ud83d\udc71",fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"\ud83e\uddd4",fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"\ud83e\uddd3",fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"\ud83d\udc74",fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"\ud83d\udc75",fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"\ud83d\udc72",fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"\ud83e\uddd5",fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"\ud83d\udc73\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"\ud83d\udc73",fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"\ud83d\udc6e\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"\ud83d\udc6e",fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"\ud83d\udc77\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"\ud83d\udc77",fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"\ud83d\udc82\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"\ud83d\udc82",fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"\ud83d\udd75\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"\ud83d\udd75",fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"\ud83d\udc69\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"\ud83d\udc68\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udf3e",fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"\ud83d\udc68\u200d\ud83c\udf3e",fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udf73",fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:"\ud83d\udc68\u200d\ud83c\udf73",fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udf93",fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:"\ud83d\udc68\u200d\ud83c\udf93",fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udfa4",fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"\ud83d\udc68\u200d\ud83c\udfa4",fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udfeb",fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"\ud83d\udc68\u200d\ud83c\udfeb",fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udfed",fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"\ud83d\udc68\u200d\ud83c\udfed",fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"\ud83d\udc69\u200d\ud83d\udcbb",fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"\ud83d\udc68\u200d\ud83d\udcbb",fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"\ud83d\udc69\u200d\ud83d\udcbc",fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"\ud83d\udc68\u200d\ud83d\udcbc",fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"\ud83d\udc69\u200d\ud83d\udd27",fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"\ud83d\udc68\u200d\ud83d\udd27",fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"\ud83d\udc69\u200d\ud83d\udd2c",fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"\ud83d\udc68\u200d\ud83d\udd2c",fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"\ud83d\udc69\u200d\ud83c\udfa8",fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:"\ud83d\udc68\u200d\ud83c\udfa8",fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"\ud83d\udc69\u200d\ud83d\ude92",fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"\ud83d\udc68\u200d\ud83d\ude92",fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"\ud83d\udc69\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"\ud83d\udc68\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"\ud83d\udc69\u200d\ud83d\ude80",fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"\ud83d\udc68\u200d\ud83d\ude80",fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"\ud83d\udc69\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"\ud83d\udc68\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"\ud83e\uddb8\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"\ud83e\uddb8\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"\ud83e\uddb9\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"\ud83e\uddb9\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"\ud83e\udd36",fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"\ud83c\udf85",fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"\ud83e\uddd9\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"\ud83e\uddd9\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:"\ud83e\udddd\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:"\ud83e\udddd\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:"\ud83e\udddb\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"\ud83e\udddb\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"\ud83e\udddf\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"\ud83e\udddf\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:"\ud83e\uddde\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:"\ud83e\uddde\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"\ud83e\udddc\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:"\ud83e\udddc\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:"\ud83e\uddda\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:"\ud83e\uddda\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"\ud83d\udc7c",fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:"\ud83e\udd30",fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"\ud83e\udd31",fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"\ud83d\udc78",fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"\ud83e\udd34",fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"\ud83d\udc70",fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"\ud83e\udd35",fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"\ud83c\udfc3\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"\ud83c\udfc3",fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"\ud83d\udeb6\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"\ud83d\udeb6",fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"\ud83d\udc83",fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"\ud83d\udd7a",fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"\ud83d\udc6f",fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"\ud83d\udc6f\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"\ud83d\udc6b",fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"\ud83d\udc6c",fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"\ud83d\udc6d",fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"\ud83d\ude47\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"\ud83d\ude47",fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"\ud83e\udd26\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"\ud83e\udd26\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"\ud83e\udd37",fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"\ud83e\udd37\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"\ud83d\udc81",fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"\ud83d\udc81\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"\ud83d\ude45",fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"\ud83d\ude45\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"\ud83d\ude46",fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"\ud83d\ude46\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"\ud83d\ude4b",fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"\ud83d\ude4b\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"\ud83d\ude4e",fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"\ud83d\ude4e\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"\ud83d\ude4d",fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"\ud83d\ude4d\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"\ud83d\udc87",fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"\ud83d\udc87\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"\ud83d\udc86",fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"\ud83d\udc86\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"\ud83e\uddd6\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"\ud83e\uddd6\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\ud83d\udc91",fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc69",fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68",fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\ud83d\udc8f",fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69",fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"\ud83d\udc6a",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"\ud83d\udc69\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"\ud83d\udc69\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"\ud83d\udc68\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"\ud83d\udc68\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66",fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d\udc67",fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"\ud83e\uddf6",fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"\ud83e\uddf5",fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:"\ud83e\udde5",fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"\ud83e\udd7c",fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"\ud83d\udc5a",fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"\ud83d\udc55",fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:"\ud83d\udc56",fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"\ud83d\udc54",fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"\ud83d\udc57",fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"\ud83d\udc59",fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"\ud83d\udc58",fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"\ud83d\udc84",fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"\ud83d\udc8b",fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"\ud83d\udc63",fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"\ud83e\udd7f",fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"\ud83d\udc60",fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"\ud83d\udc61",fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:"\ud83d\udc62",fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"\ud83d\udc5e",fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"\ud83d\udc5f",fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"\ud83e\udd7e",fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:"\ud83e\udde6",fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"\ud83e\udde4",fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"\ud83e\udde3",fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"\ud83d\udc52",fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"\ud83c\udfa9",fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"\ud83e\udde2",fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"\u26d1",fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"\ud83c\udf93",fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"\ud83d\udc51",fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"\ud83c\udf92",fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:"\ud83e\uddf3",fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"\ud83d\udc5d",fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"\ud83d\udc5b",fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"\ud83d\udc5c",fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"\ud83d\udcbc",fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"\ud83d\udc53",fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"\ud83d\udd76",fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"\ud83e\udd7d",fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"\ud83d\udc8d",fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"\ud83c\udf02",fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"\ud83d\udc36",fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"\ud83d\udc31",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"\ud83d\udc2d",fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"\ud83d\udc39",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"\ud83d\udc30",fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"\ud83e\udd8a",fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"\ud83d\udc3b",fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"\ud83d\udc3c",fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"\ud83d\udc28",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"\ud83d\udc2f",fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"\ud83e\udd81",fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\ud83d\udc2e",fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"\ud83d\udc37",fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"\ud83d\udc3d",fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"\ud83d\udc38",fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"\ud83e\udd91",fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"\ud83d\udc19",fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"\ud83e\udd90",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"\ud83d\udc35",fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"\ud83e\udd8d",fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"\ud83d\ude48",fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"\ud83d\ude49",fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"\ud83d\ude4a",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"\ud83d\udc12",fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"\ud83d\udc14",fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"\ud83d\udc27",fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"\ud83d\udc26",fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"\ud83d\udc24",fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"\ud83d\udc23",fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"\ud83d\udc25",fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"\ud83e\udd86",fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"\ud83e\udd85",fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"\ud83e\udd89",fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"\ud83e\udd87",fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"\ud83d\udc3a",fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"\ud83d\udc17",fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"\ud83d\udc34",fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"\ud83e\udd84",fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"\ud83d\udc1d",fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"\ud83d\udc1b",fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"\ud83e\udd8b",fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"\ud83d\udc0c",fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"\ud83d\udc1e",fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"\ud83d\udc1c",fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"\ud83e\udd97",fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"\ud83d\udd77",fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"\ud83e\udd82",fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"\ud83e\udd80",fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"\ud83d\udc0d",fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"\ud83e\udd8e",fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"\ud83e\udd96",fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"\ud83e\udd95",fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"\ud83d\udc22",fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"\ud83d\udc20",fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"\ud83d\udc1f",fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"\ud83d\udc21",fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"\ud83d\udc2c",fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"\ud83e\udd88",fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"\ud83d\udc33",fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"\ud83d\udc0b",fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"\ud83d\udc0a",fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"\ud83d\udc06",fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"\ud83e\udd93",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"\ud83d\udc05",fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"\ud83d\udc03",fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"\ud83d\udc02",fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\ud83d\udc04",fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"\ud83e\udd8c",fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"\ud83d\udc2a",fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"\ud83d\udc2b",fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"\ud83e\udd92",fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"\ud83d\udc18",fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"\ud83e\udd8f",fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"\ud83d\udc10",fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"\ud83d\udc0f",fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"\ud83d\udc11",fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"\ud83d\udc0e",fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"\ud83d\udc16",fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"\ud83d\udc00",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"\ud83d\udc01",fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"\ud83d\udc13",fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"\ud83e\udd83",fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"\ud83d\udd4a",fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"\ud83d\udc15",fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"\ud83d\udc29",fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"\ud83d\udc08",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"\ud83d\udc07",fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"\ud83d\udc3f",fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"\ud83e\udd94",fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"\ud83e\udd9d",fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"\ud83e\udd99",fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"\ud83e\udd9b",fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"\ud83e\udd98",fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"\ud83e\udda1",fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"\ud83e\udda2",fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"\ud83e\udd9a",fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"\ud83e\udd9c",fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"\ud83e\udd9e",fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"\ud83e\udd9f",fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"\ud83d\udc3e",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"\ud83d\udc09",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"\ud83d\udc32",fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"\ud83c\udf35",fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"\ud83c\udf84",fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"\ud83c\udf32",fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"\ud83c\udf33",fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"\ud83c\udf34",fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"\ud83c\udf31",fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"\ud83c\udf3f",fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"\u2618",fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"\ud83c\udf40",fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"\ud83c\udf8d",fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"\ud83c\udf8b",fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"\ud83c\udf43",fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"\ud83c\udf42",fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"\ud83c\udf41",fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"\ud83c\udf3e",fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"\ud83c\udf3a",fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"\ud83c\udf3b",fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"\ud83c\udf39",fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"\ud83e\udd40",fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"\ud83c\udf37",fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"\ud83c\udf3c",fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"\ud83c\udf38",fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"\ud83d\udc90",fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"\ud83c\udf44",fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"\ud83c\udf30",fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"\ud83c\udf83",fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"\ud83d\udc1a",fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"\ud83d\udd78",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"\ud83c\udf0e",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"\ud83c\udf0d",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"\ud83c\udf0f",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf15",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"\ud83c\udf16",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf17",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf18",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf11",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf12",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf13",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"\ud83c\udf14",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf1a",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf1d",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf1b",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\ud83c\udf1c",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"\ud83c\udf1e",fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"\ud83c\udf19",fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"\u2b50",fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"\ud83c\udf1f",fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"\ud83d\udcab",fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"\u2728",fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:"\u2604",fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"\u2600\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"\ud83c\udf24",fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"\u26c5",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"\ud83c\udf25",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"\ud83c\udf26",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"\u2601\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"\ud83c\udf27",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"\u26c8",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"\ud83c\udf29",fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"\u26a1",fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"\ud83d\udd25",fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"\ud83d\udca5",fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"\u2744\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"\ud83c\udf28",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"\u26c4",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"\u2603",fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"\ud83c\udf2c",fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"\ud83d\udca8",fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"\ud83c\udf2a",fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:"\ud83c\udf2b",fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"\u2602",fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"\u2614",fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"\ud83d\udca7",fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"\ud83d\udca6",fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"\ud83c\udf0a",fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"\ud83c\udf4f",fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"\ud83c\udf4e",fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"\ud83c\udf50",fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"\ud83c\udf4a",fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"\ud83c\udf4b",fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"\ud83c\udf4c",fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"\ud83c\udf49",fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"\ud83c\udf47",fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"\ud83c\udf53",fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"\ud83c\udf48",fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"\ud83c\udf52",fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"\ud83c\udf51",fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"\ud83c\udf4d",fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"\ud83e\udd65",fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"\ud83e\udd5d",fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"\ud83e\udd6d",fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"\ud83e\udd51",fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"\ud83e\udd66",fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"\ud83c\udf45",fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"\ud83c\udf46",fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"\ud83e\udd52",fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"\ud83e\udd55",fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"\ud83c\udf36",fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"\ud83e\udd54",fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"\ud83c\udf3d",fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"\ud83e\udd6c",fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"\ud83c\udf60",fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"\ud83e\udd5c",fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"\ud83c\udf6f",fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"\ud83e\udd50",fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"\ud83c\udf5e",fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"\ud83e\udd56",fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"\ud83e\udd6f",fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"\ud83e\udd68",fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"\ud83e\uddc0",fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"\ud83e\udd5a",fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"\ud83e\udd53",fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"\ud83e\udd69",fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"\ud83e\udd5e",fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"\ud83c\udf57",fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"\ud83c\udf56",fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"\ud83e\uddb4",fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"\ud83c\udf64",fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"\ud83c\udf73",fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"\ud83c\udf54",fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"\ud83c\udf5f",fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"\ud83e\udd59",fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"\ud83c\udf2d",fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"\ud83c\udf55",fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"\ud83e\udd6a",fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"\ud83e\udd6b",fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"\ud83c\udf5d",fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"\ud83c\udf2e",fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"\ud83c\udf2f",fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"\ud83e\udd57",fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"\ud83e\udd58",fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"\ud83c\udf5c",fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"\ud83c\udf72",fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"\ud83c\udf65",fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"\ud83e\udd60",fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"\ud83c\udf63",fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"\ud83c\udf71",fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"\ud83c\udf5b",fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"\ud83c\udf59",fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"\ud83c\udf5a",fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"\ud83c\udf58",fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"\ud83c\udf62",fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"\ud83c\udf61",fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"\ud83c\udf67",fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"\ud83c\udf68",fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"\ud83c\udf66",fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"\ud83e\udd67",fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"\ud83c\udf70",fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"\ud83e\uddc1",fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"\ud83e\udd6e",fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"\ud83c\udf82",fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"\ud83c\udf6e",fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"\ud83c\udf6c",fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"\ud83c\udf6d",fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"\ud83c\udf6b",fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"\ud83c\udf7f",fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"\ud83e\udd5f",fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"\ud83c\udf69",fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"\ud83c\udf6a",fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"\ud83e\udd5b",fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\ud83c\udf7a",fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\ud83c\udf7b",fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"\ud83e\udd42",fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"\ud83c\udf77",fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"\ud83e\udd43",fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"\ud83c\udf78",fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"\ud83c\udf79",fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"\ud83c\udf7e",fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"\ud83c\udf76",fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"\ud83c\udf75",fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"\ud83e\udd64",fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"\u2615",fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"\ud83c\udf7c",fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"\ud83e\uddc2",fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"\ud83e\udd44",fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"\ud83c\udf74",fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"\ud83c\udf7d",fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"\ud83e\udd63",fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"\ud83e\udd61",fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"\ud83e\udd62",fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"\u26bd",fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"\ud83c\udfc0",fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"\ud83c\udfc8",fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:"\u26be",fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:"\ud83e\udd4e",fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"\ud83c\udfbe",fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:"\ud83c\udfd0",fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:"\ud83c\udfc9",fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"\ud83e\udd4f",fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"\ud83c\udfb1",fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"\u26f3",fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"\ud83c\udfcc\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:"\ud83c\udfcc",fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"\ud83c\udfd3",fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:"\ud83c\udff8",fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:"\ud83e\udd45",fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:"\ud83c\udfd2",fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:"\ud83c\udfd1",fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"\ud83e\udd4d",fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:"\ud83c\udfcf",fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"\ud83c\udfbf",fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"\u26f7",fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"\ud83c\udfc2",fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"\ud83e\udd3a",fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"\ud83e\udd3c\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"\ud83e\udd3c\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"\ud83e\udd38\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"\ud83e\udd38\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"\ud83e\udd3e\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:"\ud83e\udd3e\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:"\u26f8",fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:"\ud83e\udd4c",fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:"\ud83d\udef9",fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"\ud83d\udef7",fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"\ud83c\udff9",fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"\ud83c\udfa3",fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"\ud83e\udd4a",fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"\ud83e\udd4b",fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"\ud83d\udea3\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"\ud83d\udea3",fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"\ud83e\uddd7\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"\ud83e\uddd7\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"\ud83c\udfca\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"\ud83c\udfca",fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"\ud83e\udd3d\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"\ud83e\udd3d\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"\ud83e\uddd8\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"\ud83e\uddd8\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"\ud83c\udfc4\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"\ud83c\udfc4",fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"\ud83d\udec0",fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"\u26f9\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:"\u26f9",fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"\ud83c\udfcb\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"\ud83c\udfcb",fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"\ud83d\udeb4\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"\ud83d\udeb4",fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"\ud83d\udeb5\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"\ud83d\udeb5",fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"\ud83c\udfc7",fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"\ud83d\udd74",fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"\ud83c\udfc6",fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"\ud83c\udfbd",fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:"\ud83c\udfc5",fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"\ud83c\udf96",fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"\ud83e\udd47",fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"\ud83e\udd48",fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"\ud83e\udd49",fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"\ud83c\udf97",fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"\ud83c\udff5",fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"\ud83c\udfab",fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"\ud83c\udf9f",fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"\ud83c\udfad",fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"\ud83c\udfa8",fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"\ud83c\udfaa",fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\ud83e\udd39\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\ud83e\udd39\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"\ud83c\udfa4",fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"\ud83c\udfa7",fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"\ud83c\udfbc",fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"\ud83c\udfb9",fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"\ud83e\udd41",fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"\ud83c\udfb7",fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:"\ud83c\udfba",fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:"\ud83c\udfb8",fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"\ud83c\udfbb",fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:"\ud83c\udfac",fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"\ud83c\udfae",fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"\ud83d\udc7e",fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"\ud83c\udfaf",fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"\ud83c\udfb2",fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"\ud83c\udfb0",fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"\ud83e\udde9",fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"\ud83c\udfb3",fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"\ud83d\ude97",fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"\ud83d\ude95",fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"\ud83d\ude99",fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"\ud83d\ude8c",fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"\ud83d\ude8e",fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"\ud83c\udfce",fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"\ud83d\ude93",fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"\ud83d\ude91",fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"\ud83d\ude92",fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"\ud83d\ude90",fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"\ud83d\ude9a",fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"\ud83d\ude9b",fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"\ud83d\ude9c",fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"\ud83d\udef4",fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"\ud83c\udfcd",fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"\ud83d\udeb2",fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"\ud83d\udef5",fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"\ud83d\udea8",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"\ud83d\ude94",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"\ud83d\ude8d",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"\ud83d\ude98",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"\ud83d\ude96",fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"\ud83d\udea1",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"\ud83d\udea0",fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"\ud83d\ude9f",fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"\ud83d\ude83",fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"\ud83d\ude8b",fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"\ud83d\ude9d",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"\ud83d\ude84",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"\ud83d\ude85",fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"\ud83d\ude88",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"\ud83d\ude9e",fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"\ud83d\ude82",fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"\ud83d\ude86",fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"\ud83d\ude87",fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"\ud83d\ude8a",fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"\ud83d\ude89",fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"\ud83d\udef8",fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"\ud83d\ude81",fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"\ud83d\udee9",fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"\u2708\ufe0f",fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"\ud83d\udeeb",fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"\ud83d\udeec",fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"\u26f5",fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"\ud83d\udee5",fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"\ud83d\udea4",fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"\u26f4",fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"\ud83d\udef3",fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"\ud83d\ude80",fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"\ud83d\udef0",fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"\ud83d\udcba",fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"\ud83d\udef6",fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"\u2693",fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"\ud83d\udea7",fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"\u26fd",fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"\ud83d\ude8f",fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"\ud83d\udea6",fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"\ud83d\udea5",fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"\ud83c\udfc1",fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"\ud83d\udea2",fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"\ud83c\udfa1",fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"\ud83c\udfa2",fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"\ud83c\udfa0",fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"\ud83c\udfd7",fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"\ud83c\udf01",fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"\ud83d\uddfc",fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"\ud83c\udfed",fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"\u26f2",fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"\ud83c\udf91",fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"\u26f0",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"\ud83c\udfd4",fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"\ud83d\uddfb",fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"\ud83c\udf0b",fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"\ud83d\uddfe",fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"\ud83c\udfd5",fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"\u26fa",fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"\ud83c\udfde",fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"\ud83d\udee3",fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"\ud83d\udee4",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"\ud83c\udf05",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"\ud83c\udf04",fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"\ud83c\udfdc",fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"\ud83c\udfd6",fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"\ud83c\udfdd",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"\ud83c\udf07",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"\ud83c\udf06",fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"\ud83c\udfd9",fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"\ud83c\udf03",fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"\ud83c\udf09",fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"\ud83c\udf0c",fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"\ud83c\udf20",fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"\ud83c\udf87",fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"\ud83c\udf86",fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"\ud83c\udf08",fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"\ud83c\udfd8",fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"\ud83c\udff0",fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"\ud83c\udfef",fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"\ud83c\udfdf",fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"\ud83d\uddfd",fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:"\ud83c\udfe0",fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"\ud83c\udfe1",fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"\ud83c\udfda",fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"\ud83c\udfe2",fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"\ud83c\udfec",fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"\ud83c\udfe3",fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"\ud83c\udfe4",fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"\ud83c\udfe5",fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"\ud83c\udfe6",fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"\ud83c\udfe8",fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"\ud83c\udfea",fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"\ud83c\udfeb",fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"\ud83c\udfe9",fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"\ud83d\udc92",fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"\ud83c\udfdb",fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"\u26ea",fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"\ud83d\udd4c",fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"\ud83d\udd4d",fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"\ud83d\udd4b",fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"\u26e9",fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"\u231a",fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"\ud83d\udcf1",fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:"\ud83d\udcf2",fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"\ud83d\udcbb",fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"\u2328",fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"\ud83d\udda5",fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:"\ud83d\udda8",fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:"\ud83d\uddb1",fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"\ud83d\uddb2",fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:"\ud83d\udd79",fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:"\ud83d\udddc",fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"\ud83d\udcbd",fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"\ud83d\udcbe",fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"\ud83d\udcbf",fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"\ud83d\udcc0",fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"\ud83d\udcfc",fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:"\ud83d\udcf7",fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"\ud83d\udcf8",fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:"\ud83d\udcf9",fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:"\ud83c\udfa5",fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"\ud83d\udcfd",fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:"\ud83c\udf9e",fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"\ud83d\udcde",fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"\u260e\ufe0f",fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"\ud83d\udcdf",fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:"\ud83d\udce0",fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"\ud83d\udcfa",fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"\ud83d\udcfb",fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"\ud83c\udf99",fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:"\ud83c\udf9a",fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:"\ud83c\udf9b",fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"\ud83e\udded",fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"\u23f1",fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:"\u23f2",fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"\u23f0",fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"\ud83d\udd70",fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"\u23f3",fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"\u231b",fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"\ud83d\udce1",fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"\ud83d\udd0b",fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:"\ud83d\udd0c",fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"\ud83d\udca1",fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"\ud83d\udd26",fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:"\ud83d\udd6f",fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"\ud83e\uddef",fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"\ud83d\uddd1",fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:"\ud83d\udee2",fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"\ud83d\udcb8",fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"\ud83d\udcb5",fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"\ud83d\udcb4",fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"\ud83d\udcb6",fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"\ud83d\udcb7",fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"\ud83d\udcb0",fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"\ud83d\udcb3",fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"\ud83d\udc8e",fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"\u2696",fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"\ud83e\uddf0",fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"\ud83d\udd27",fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:"\ud83d\udd28",fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"\u2692",fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"\ud83d\udee0",fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:"\u26cf",fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"\ud83d\udd29",fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:"\u2699",fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:"\ud83e\uddf1",fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:"\u26d3",fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"\ud83e\uddf2",fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"\ud83d\udd2b",fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"\ud83d\udca3",fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"\ud83e\udde8",fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"\ud83d\udd2a",fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:"\ud83d\udde1",fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:"\u2694",fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:"\ud83d\udee1",fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"\ud83d\udeac",fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"\u2620",fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"\u26b0",fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"\u26b1",fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:"\ud83c\udffa",fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"\ud83d\udd2e",fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"\ud83d\udcff",fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"\ud83e\uddff",fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:"\ud83d\udc88",fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"\u2697",fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"\ud83d\udd2d",fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"\ud83d\udd2c",fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:"\ud83d\udd73",fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"\ud83d\udc8a",fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"\ud83d\udc89",fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"\ud83e\uddec",fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"\ud83e\udda0",fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"\ud83e\uddeb",fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"\ud83e\uddea",fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"\ud83c\udf21",fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"\ud83e\uddf9",fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:"\ud83e\uddfa",fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:"\ud83e\uddfb",fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:"\ud83c\udff7",fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"\ud83d\udd16",fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"\ud83d\udebd",fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"\ud83d\udebf",fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"\ud83d\udec1",fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"\ud83e\uddfc",fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"\ud83e\uddfd",fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"\ud83e\uddf4",fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:"\ud83d\udd11",fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:"\ud83d\udddd",fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"\ud83d\udecb",fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"\ud83d\udecc",fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:"\ud83d\udecf",fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:"\ud83d\udeaa",fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:"\ud83d\udece",fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"\ud83e\uddf8",fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:"\ud83d\uddbc",fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:"\ud83d\uddfa",fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"\u26f1",fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"\ud83d\uddff",fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"\ud83d\udecd",fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:"\ud83d\uded2",fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"\ud83c\udf88",fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"\ud83c\udf8f",fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"\ud83c\udf80",fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"\ud83c\udf81",fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"\ud83c\udf8a",fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"\ud83c\udf89",fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"\ud83c\udf8e",fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"\ud83c\udf90",fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"\ud83c\udf8c",fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"\ud83c\udfee",fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:"\ud83e\udde7",fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"\u2709\ufe0f",fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"\ud83d\udce9",fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"\ud83d\udce8",fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"\ud83d\udce7",fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"\ud83d\udc8c",fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"\ud83d\udcee",fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"\ud83d\udcea",fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"\ud83d\udceb",fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"\ud83d\udcec",fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"\ud83d\udced",fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"\ud83d\udce6",fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"\ud83d\udcef",fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"\ud83d\udce5",fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"\ud83d\udce4",fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"\ud83d\udcdc",fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"\ud83d\udcc3",fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"\ud83d\udcd1",fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"\ud83e\uddfe",fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"\ud83d\udcca",fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"\ud83d\udcc8",fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"\ud83d\udcc9",fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"\ud83d\udcc4",fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:"\ud83d\udcc5",fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"\ud83d\udcc6",fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"\ud83d\uddd3",fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:"\ud83d\udcc7",fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"\ud83d\uddc3",fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:"\ud83d\uddf3",fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"\ud83d\uddc4",fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"\ud83d\udccb",fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"\ud83d\uddd2",fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"\ud83d\udcc1",fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"\ud83d\udcc2",fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"\ud83d\uddc2",fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"\ud83d\uddde",fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:"\ud83d\udcf0",fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"\ud83d\udcd3",fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"\ud83d\udcd5",fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"\ud83d\udcd7",fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"\ud83d\udcd8",fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"\ud83d\udcd9",fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"\ud83d\udcd4",fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:"\ud83d\udcd2",fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:"\ud83d\udcda",fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"\ud83d\udcd6",fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:"\ud83e\uddf7",fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:"\ud83d\udd17",fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"\ud83d\udcce",fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"\ud83d\udd87",fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:"\u2702\ufe0f",fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"\ud83d\udcd0",fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"\ud83d\udccf",fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:"\ud83e\uddee",fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"\ud83d\udccc",fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"\ud83d\udccd",fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"\ud83d\udea9",fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"\ud83c\udff3",fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:"\ud83c\udff4",fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"\ud83c\udff3\ufe0f\u200d\ud83c\udf08",fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"\ud83d\udd10",fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:"\ud83d\udd12",fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:"\ud83d\udd13",fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"\ud83d\udd0f",fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"\ud83d\udd8a",fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"\ud83d\udd8b",fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"\u2712\ufe0f",fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"\ud83d\udcdd",fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"\u270f\ufe0f",fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"\ud83d\udd8d",fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"\ud83d\udd8c",fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"\ud83d\udd0d",fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"\ud83d\udd0e",fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:"\u2764\ufe0f",fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"\ud83e\udde1",fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc9b",fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc9a",fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc99",fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc9c",fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:"\ud83d\udda4",fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"\ud83d\udc94",fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"\u2763",fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"\ud83d\udc95",fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc9e",fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"\ud83d\udc93",fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"\ud83d\udc97",fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"\ud83d\udc96",fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"\ud83d\udc98",fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"\ud83d\udc9d",fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"\ud83d\udc9f",fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"\u262e",fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:"\u271d",fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"\u262a",fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\ud83d\udd49",fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\u2638",fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:"\u2721",fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"\ud83d\udd2f",fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"\ud83d\udd4e",fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:"\u262f",fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"\u2626",fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"\ud83d\uded0",fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"\u26ce",fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2648",fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2649",fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264a",fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264b",fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264c",fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264d",fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264e",fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"\u264f",fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2650",fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2651",fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2652",fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2653",fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:"\ud83c\udd94",fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"\u269b",fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"\ud83c\ude33",fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"\ud83c\ude39",fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"\u2622",fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:"\u2623",fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"\ud83d\udcf4",fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"\ud83d\udcf3",fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"\ud83c\ude36",fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"\ud83c\ude1a",fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"\ud83c\ude38",fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"\ud83c\ude3a",fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"\ud83c\ude37\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"\u2734\ufe0f",fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:"\ud83c\udd9a",fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"\ud83c\ude51",fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"\ud83d\udcae",fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"\ud83c\ude50",fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"\u3299\ufe0f",fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"\u3297\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"\ud83c\ude34",fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"\ud83c\ude35",fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"\ud83c\ude32",fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"\ud83c\udd70\ufe0f",fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"\ud83c\udd71\ufe0f",fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"\ud83c\udd8e",fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"\ud83c\udd91",fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"\ud83c\udd7e\ufe0f",fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"\ud83c\udd98",fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"\u26d4",fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"\ud83d\udcdb",fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"\ud83d\udeab",fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"\u274c",fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:"\u2b55",fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:"\ud83d\uded1",fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:"\ud83d\udca2",fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"\u2668\ufe0f",fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"\ud83d\udeb7",fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"\ud83d\udeaf",fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"\ud83d\udeb3",fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"\ud83d\udeb1",fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"\ud83d\udd1e",fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"\ud83d\udcf5",fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"\u2757",fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"\u2755",fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:"\u2753",fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"\u2754",fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"\u203c\ufe0f",fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"\u2049\ufe0f",fitzpatrick_scale:!1,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"\ud83d\udcaf",fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"\ud83d\udd05",fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"\ud83d\udd06",fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:"\ud83d\udd31",fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"\u269c",fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"\u303d\ufe0f",fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"\u26a0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"\ud83d\udeb8",fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:"\ud83d\udd30",fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"\u267b\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"\ud83c\ude2f",fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"\ud83d\udcb9",fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"\u2747\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"\u2733\ufe0f",fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"\u274e",fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"\u2705",fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"\ud83d\udca0",fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"\ud83c\udf00",fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:"\u27bf",fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"\ud83c\udf10",fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"\u24c2\ufe0f",fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"\ud83c\udfe7",fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"\ud83c\ude02\ufe0f",fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"\ud83d\udec2",fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"\ud83d\udec3",fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"\ud83d\udec4",fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"\ud83d\udec5",fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"\u267f",fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"\ud83d\udead",fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"\ud83d\udebe",fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"\ud83c\udd7f\ufe0f",fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"\ud83d\udeb0",fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"\ud83d\udeb9",fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"\ud83d\udeba",fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"\ud83d\udebc",fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"\ud83d\udebb",fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"\ud83d\udeae",fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"\ud83c\udfa6",fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"\ud83d\udcf6",fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"\ud83c\ude01",fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"\ud83c\udd96",fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"\ud83c\udd97",fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"\ud83c\udd99",fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:"\ud83c\udd92",fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"\ud83c\udd95",fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:"\ud83c\udd93",fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"\ud83d\udd1f",fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*\u20e3",fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"\ud83d\udd22",fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:"\u23cf\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"\u25b6\ufe0f",fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"\u23f8",fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"\u23ed",fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:"\u23f9",fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:"\u23fa",fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"\u23ef",fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:"\u23ee",fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"\u23e9",fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"\u23ea",fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"\ud83d\udd00",fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:"\ud83d\udd01",fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"\ud83d\udd02",fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"\u25c0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"\ud83d\udd3c",fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"\ud83d\udd3d",fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"\u23eb",fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"\u23ec",fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"\u27a1\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"\u2b05\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"\u2b06\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"\u2b07\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"\u2197\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"\u2198\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"\u2199\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"\u2196\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"\u2195\ufe0f",fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"\u2194\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"\ud83d\udd04",fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"\u21aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"\u21a9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"\u2934\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"\u2935\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"\u2139\ufe0f",fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"\ud83d\udd24",fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"\ud83d\udd21",fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"\ud83d\udd20",fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"\ud83d\udd23",fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"\ud83c\udfb5",fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:"\ud83c\udfb6",fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"\u3030\ufe0f",fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"\u27b0",fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"\u2714\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"\ud83d\udd03",fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"\u2795",fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"\u2796",fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"\u2797",fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"\u2716\ufe0f",fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:"\u267e",fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"\ud83d\udcb2",fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"\ud83d\udcb1",fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"\xa9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"\xae\ufe0f",fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"\u2122\ufe0f",fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:"\ud83d\udd1a",fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:"\ud83d\udd19",fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:"\ud83d\udd1b",fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:"\ud83d\udd1d",fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:"\ud83d\udd1c",fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"\u2611\ufe0f",fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"\ud83d\udd18",fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:"\u26aa",fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"\u26ab",fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"\ud83d\udd34",fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"\ud83d\udd35",fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"\ud83d\udd38",fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"\ud83d\udd39",fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"\ud83d\udd36",fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"\ud83d\udd37",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"\ud83d\udd3a",fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"\u25aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"\u25ab\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"\u2b1b",fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"\u2b1c",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"\ud83d\udd3b",fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"\u25fc\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"\u25fb\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"\u25fe",fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"\u25fd",fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"\ud83d\udd32",fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"\ud83d\udd33",fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"\ud83d\udd08",fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"\ud83d\udd09",fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"\ud83d\udd0a",fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"\ud83d\udd07",fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"\ud83d\udce3",fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"\ud83d\udce2",fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"\ud83d\udd14",fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"\ud83d\udd15",fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"\ud83c\udccf",fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"\ud83c\udc04",fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"\u2660\ufe0f",fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"\u2663\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"\u2665\ufe0f",fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"\u2666\ufe0f",fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"\ud83c\udfb4",fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"\ud83d\udcad",fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"\ud83d\uddef",fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"\ud83d\udcac",fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"\ud83d\udde8",fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"\ud83d\udd50",fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"\ud83d\udd51",fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"\ud83d\udd52",fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"\ud83d\udd53",fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"\ud83d\udd54",fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"\ud83d\udd55",fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"\ud83d\udd56",fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"\ud83d\udd57",fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"\ud83d\udd58",fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"\ud83d\udd59",fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"\ud83d\udd5a",fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"\ud83d\udd5b",fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"\ud83d\udd5c",fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"\ud83d\udd5d",fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"\ud83d\udd5e",fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"\ud83d\udd5f",fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"\ud83d\udd60",fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"\ud83d\udd61",fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"\ud83d\udd62",fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"\ud83d\udd63",fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"\ud83d\udd64",fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"\ud83d\udd65",fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"\ud83d\udd66",fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"\ud83d\udd67",fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddfd",fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"\ud83c\udde9\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf6",fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\udde7",fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddef",fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf6",fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddfb",fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"\ud83c\udde8\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddfd",fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"\ud83c\udded\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"\ud83c\udde9\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"\ud83c\udde9\ud83c\uddef",fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"\ud83c\udde9\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"\ud83c\udde9\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddfb",fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf6",fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"\ud83c\uddea\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"\ud83c\uddeb\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"\ud83c\uddeb\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"\ud83c\uddeb\ud83c\uddef",fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"\ud83c\uddeb\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"\ud83c\uddeb\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"\ud83c\udde9\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf5",fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"\ud83c\udded\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"\ud83c\udded\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"\ud83c\udded\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"\ud83c\udded\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf6",fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"\ud83c\uddee\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"\ud83c\uddef\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"\ud83c\uddef\ud83c\uddf5",fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"\ud83c\uddef\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"\ud83c\uddef\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"\ud83c\uddfd\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddfb",fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\udde7",fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddfb",fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf6",fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"\ud83c\uddfe\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddfd",fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"\ud83c\uddeb\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddf5",fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"\ud83c\uddf2\ud83c\uddf5",fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"\ud83c\uddf0\ud83c\uddf5",fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"\ud83c\uddf3\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"\ud83c\uddf4\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"\ud83c\uddf6\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:"\ud83c\uddf7\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"\ud83c\uddf7\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"\ud83c\uddf7\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"\ud83c\uddf7\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:"\ud83c\udde7\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"\ud83c\uddf0\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"\ud83c\uddf5\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"\ud83c\uddfc\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"\ud83c\uddf7\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddfd",fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\udde7",fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"\ud83c\uddff\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"\ud83c\uddec\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"\ud83c\uddf0\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"\ud83c\uddf1\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\udde9",fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"\ud83c\udde8\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"\ud83c\uddf8\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddef",fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf1",fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf0",fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf4",fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf9",fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf7",fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\udde8",fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"\ud83c\uddf9\ud83c\uddfb",fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"\ud83c\uddfa\ud83c\uddec",fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"\ud83c\uddfa\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"\ud83c\udde6\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"\ud83c\uddec\ud83c\udde7",fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f",fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f",fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f",fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"\ud83c\uddfa\ud83c\uddf8",fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\uddee",fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"\ud83c\uddfa\ud83c\uddfe",fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"\ud83c\uddfa\ud83c\uddff",fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\uddfa",fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\udde6",fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"\ud83c\uddfb\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"\ud83c\uddfc\ud83c\uddeb",fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"\ud83c\uddea\ud83c\udded",fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"\ud83c\uddfe\ud83c\uddea",fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"\ud83c\uddff\ud83c\uddf2",fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"\ud83c\uddff\ud83c\uddfc",fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"\ud83c\uddfa\ud83c\uddf3",fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"\ud83c\udff4\u200d\u2620\ufe0f",fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/emoticons/plugin.min.js b/apps/st/public/tinymce/plugins/emoticons/plugin.min.js new file mode 100644 index 000000000..5c70cdd35 --- /dev/null +++ b/apps/st/public/tinymce/plugins/emoticons/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function r(){}function i(t){return function(){return t}}function t(t){return t}function n(){return c}var m,e=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=i(!1),a=i(!(m=null)),c={fold:function(t,n){return t()},isSome:l,isNone:a,getOr:t,getOrThunk:o,getOrDie:function(t){throw new Error(t||"error: getOrDie called on none.")},getOrNull:i(null),getOrUndefined:i(void 0),or:t,orThunk:o,map:n,each:r,bind:n,exists:l,forall:a,filter:function(){return c},toArray:function(){return[]},toString:i("none()")};function o(t){return t()}function g(t){var n=t;return{get:function(){return n},set:function(t){n=t}}}function d(t,n){for(var e=k(t),o=0,r=e.length;o<r;o++){var i=e[o];n(t[i],i)}}function h(t,n){return j.call(t,n)}function s(){var t,n,e=(t=r,n=g(O.none()),{clear:function(){o(),n.set(O.none())},isSet:function(){return n.get().isSome()},get:function(){return n.get()},set:function(t){o(),n.set(O.some(t))}});function o(){return n.get().each(t)}return C(C({},e),{on:function(t){return e.get().each(t)}})}function f(t,n){return-1!==t.indexOf(n)}function v(i,r,t){var a=s(),c=s(),f=i.getParam("emoticons_images_url","https://twemoji.maxcdn.com/v/13.0.1/72x72/","string");i.on("init",function(){_.load(t,r).then(function(t){var n,e,o,l,s,r=(n=i.getParam("emoticons_append",{},"object"),e=function(t){return C({keywords:[],category:"user"},t)},A(n,function(t,n){return{k:n,v:e(t)}}));o=w(t,r),l={},s=[],d(o,function(t,n){var e,o,r,i,a,c={title:n,keywords:t.keywords,char:(i=(r=t).char).length>=(a="<img").length&&i.substr(0,0+a.length)===a?r.char.replace(/src="([^"]+)"/,function(t,n){return'src="'+f+n+'"'}):r.char,category:h(e=D,o=t.category)?e[o]:o},u=void 0!==l[c.category]?l[c.category]:[];l[c.category]=u.concat([c]),s.push(c)}),a.set(l),c.set(s)},function(t){console.log("Failed to load emoticons: "+t),a.set({}),c.set([])})});function e(){return c.get().getOr([])}function u(){return a.isSet()&&c.isSet()}return{listCategories:function(){return[P].concat(k(a.get().getOr({})))},hasLoaded:u,waitForLoad:function(){return u()?x.resolve(!0):new x(function(t,n){var e=15,o=T.setInterval(function(){u()?(T.clearInterval(o),t(!0)):--e<0&&(console.log("Could not load emojis from url: "+r),T.clearInterval(o),n(!1))},100)})},listAll:e,listCategory:function(n){return n===P?e():a.get().bind(function(t){return O.from(t[n])}).getOr([])}}}function y(t,n,e){for(var o,r,i=[],a=n.toLowerCase(),c=e.fold(function(){return l},function(n){return function(t){return n<=t}}),u=0;u<t.length&&(0!==n.length&&(o=t[u],r=a,!f(o.title.toLowerCase(),r)&&!function(t){for(var n=0,e=t.length;n<e;n++)if(f(t[n].toLowerCase(),r))return 1}(o.keywords))||(i.push({value:t[u].char,text:t[u].title,icon:t[u].char}),!c(i.length)));u++);return i}function p(o,i){var e,r,t={pattern:"",results:y(i.listAll(),"",O.some(300))},a=g(P),c=(e=function(t){var n,e=(n=t).getData(),o=a.get(),r=y(i.listCategory(o),e[S],o===P?O.some(300):O.none());n.setData({results:r})},r=null,{cancel:u,throttle:function(){for(var t=[],n=0;n<arguments.length;n++)t[n]=arguments[n];u(),r=setTimeout(function(){r=null,e.apply(null,t)},200)}});function u(){m===r||(clearTimeout(r),r=null)}function n(){return{title:"Emoticons",size:"normal",body:{type:"tabpanel",tabs:function(t){for(var n=t.length,e=new Array(n),o=0;o<n;o++){var r=t[o];e[o]={title:r,name:r,items:[l,s]}}return e}(i.listCategories())},initialData:t,onTabChange:function(t,n){a.set(n.newTabName),c.throttle(t)},onChange:c.throttle,onAction:function(t,n){var e;"results"===n.name&&(e=n.value,o.insertContent(e),t.close())},buttons:[{type:"cancel",text:"Close",primary:!0}]}}var l={label:"Search",type:"input",name:S},s={type:"collection",name:"results"},f=o.windowManager.open(n());f.focus(S),i.hasLoaded()||(f.block("Loading emoticons..."),i.waitForLoad().then(function(){f.redial(n()),c.throttle(f),f.focus(S),f.unblock()}).catch(function(t){f.redial({title:"Emoticons",body:{type:"panel",items:[{type:"alertbanner",level:"error",icon:"warning",text:"<p>Could not load emoticons</p>"}]},buttons:[{type:"cancel",text:"Close",primary:!0}],initialData:{pattern:"",results:[]}}),f.focus(S),f.unblock()}))}function b(t){t.on("PreInit",function(){t.parser.addAttributeFilter("data-emoticon",function(t){!function(t){for(var n,e=0,o=t.length;e<o;e++)(n=t[e]).attr("data-mce-resize","false"),n.attr("data-mce-placeholder","1")}(t)})})}function w(){for(var t=[],n=0;n<arguments.length;n++)t[n]=arguments[n];if(0===t.length)throw new Error("Can't merge zero objects");for(var e={},o=0;o<t.length;o++){var r,i=t[o];for(r in i)h(i,r)&&(e[r]=(e[r],i[r]))}return e}var u=function(e){function t(){return r}function n(t){return t(e)}var o=i(e),r={fold:function(t,n){return n(e)},isSome:a,isNone:l,getOr:o,getOrThunk:o,getOrDie:o,getOrNull:o,getOrUndefined:o,or:t,orThunk:t,map:function(t){return u(t(e))},each:function(t){t(e)},bind:n,exists:n,forall:n,filter:function(t){return t(e)?r:c},toArray:function(){return[e]},toString:function(){return"some("+e+")"}};return r},O={some:u,none:n,from:function(t){return null==t?c:u(t)}},C=function(){return(C=Object.assign||function(t){for(var n,e=1,o=arguments.length;e<o;e++)for(var r in n=arguments[e])Object.prototype.hasOwnProperty.call(n,r)&&(t[r]=n[r]);return t}).apply(this,arguments)},k=Object.keys,j=Object.hasOwnProperty,A=function(t,o){var r={};return d(t,function(t,n){var e=o(t,n);r[e.k]=e.v}),r},_=tinymce.util.Tools.resolve("tinymce.Resource"),T=tinymce.util.Tools.resolve("tinymce.util.Delay"),x=tinymce.util.Tools.resolve("tinymce.util.Promise"),P="All",D={symbols:"Symbols",people:"People",animals_and_nature:"Animals and Nature",food_and_drink:"Food and Drink",activity:"Activity",travel_and_places:"Travel and Places",objects:"Objects",flags:"Flags",user:"User Defined"},S="pattern";e.add("emoticons",function(t,n){var e,o,r,i,a,c,u,l,s=(o=n,r=(e=t).getParam("emoticons_database","emojis","string"),e.getParam("emoticons_database_url",o+"/js/"+r+e.suffix+".js","string")),f=t.getParam("emoticons_database_id","tinymce.plugins.emoticons","string"),m=v(t,s,f);function g(){return c.execCommand("mceEmoticons")}a=m,(i=t).addCommand("mceEmoticons",function(){return p(i,a)}),(c=t).ui.registry.addButton("emoticons",{tooltip:"Emoticons",icon:"emoji",onAction:g}),c.ui.registry.addMenuItem("emoticons",{text:"Emoticons...",icon:"emoji",onAction:g}),l=m,(u=t).ui.registry.addAutocompleter("emoticons",{ch:":",columns:"auto",minChars:2,fetch:function(t,n){return l.waitForLoad().then(function(){return y(l.listAll(),t,O.some(n))})},onAction:function(t,n,e){u.selection.setRng(n),u.insertContent(e),t.hide()}}),b(t)})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/fullpage/plugin.min.js b/apps/st/public/tinymce/plugins/fullpage/plugin.min.js new file mode 100644 index 000000000..ca9433df5 --- /dev/null +++ b/apps/st/public/tinymce/plugins/fullpage/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function i(e){var t=e;return{get:function(){return t},set:function(e){t=e}}}function _(e){return e.getParam("fullpage_hide_in_source_view")}function b(e){return e.getParam("fullpage_default_encoding")}function x(e){return e.getParam("fullpage_default_font_family")}function k(e){return e.getParam("fullpage_default_font_size")}function C(e,t){return n({validate:!1,root_name:"#document"},e.schema).parse(t,{format:"xhtml"})}function c(u,m){u.addCommand("mceFullPageProperties",function(){var l,i,o,e,t,n,r,a,c,s;function d(e,t){return e.attr(t)||""}t=l=u,n=(i=m).get(),c=C(t,n),(s={}).fontface=x(t),s.fontsize=k(t),7===(r=c.firstChild).type&&(s.xml_pi=!0,(a=/encoding="([^"]+)"/.exec(r.value))&&(s.docencoding=a[1])),(r=c.getAll("#doctype")[0])&&(s.doctype="<!DOCTYPE"+r.value+">"),(r=c.getAll("title")[0])&&r.firstChild&&(s.title=r.firstChild.value),w.each(c.getAll("meta"),function(e){var t,n=e.attr("name"),l=e.attr("http-equiv");n?s[n.toLowerCase()]=e.attr("content"):"Content-Type"===l&&(t=/charset\s*=\s*(.*)\s*/gi.exec(e.attr("content")))&&(s.docencoding=t[1])}),(r=c.getAll("html")[0])&&(s.langcode=d(r,"lang")||d(r,"xml:lang")),s.stylesheets=[],w.each(c.getAll("link"),function(e){"stylesheet"===e.attr("rel")&&s.stylesheets.push(e.attr("href"))}),(r=c.getAll("body")[0])&&(s.langdir=d(r,"dir"),s.style=d(r,"style"),s.visited_color=d(r,"vlink"),s.link_color=d(r,"link"),s.active_color=d(r,"alink")),o=s,e=g(g({},{title:"",keywords:"",description:"",robots:"",author:"",docencoding:""}),o),l.windowManager.open({title:"Metadata and Document Properties",size:"normal",body:{type:"panel",items:[{name:"title",type:"input",label:"Title"},{name:"keywords",type:"input",label:"Keywords"},{name:"description",type:"input",label:"Description"},{name:"robots",type:"input",label:"Robots"},{name:"author",type:"input",label:"Author"},{name:"docencoding",type:"input",label:"Encoding"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:e,onSubmit:function(e){var t=e.getData(),n=function(e,o,t){function n(e,t,n){e.attr(t,n||void 0)}function r(e){s.firstChild?s.insert(e,s.firstChild):s.append(e)}var a,l,i=e.dom,c=C(e,t),s=c.getAll("head")[0];s||(a=c.getAll("html")[0],s=new f("head",1),a.firstChild?a.insert(s,a.firstChild,!0):a.append(s)),a=c.firstChild,o.xml_pi?(l='version="1.0"',o.docencoding&&(l+=' encoding="'+o.docencoding+'"'),7!==a.type&&(a=new f("xml",7),c.insert(a,c.firstChild,!0)),a.value=l):a&&7===a.type&&a.remove(),a=c.getAll("#doctype")[0],o.doctype?(a||(a=new f("#doctype",10),o.xml_pi?c.insert(a,c.firstChild):r(a)),a.value=o.doctype.substring(9,o.doctype.length-1)):a&&a.remove(),a=null,w.each(c.getAll("meta"),function(e){"Content-Type"===e.attr("http-equiv")&&(a=e)}),o.docencoding?(a||((a=new f("meta",1)).attr("http-equiv","Content-Type"),a.shortEnded=!0,r(a)),a.attr("content","text/html; charset="+o.docencoding)):a&&a.remove(),a=c.getAll("title")[0],o.title?(a?a.empty():r(a=new f("title",1)),a.append(new f("#text",3)).value=o.title):a&&a.remove(),w.each("keywords,description,author,copyright,robots".split(","),function(e){for(var t,n=c.getAll("meta"),l=o[e],i=0;i<n.length;i++)if((t=n[i]).attr("name")===e)return void(l?t.attr("content",l):t.remove());l&&((a=new f("meta",1)).attr("name",e),a.attr("content",l),a.shortEnded=!0,r(a))});var d={};w.each(c.getAll("link"),function(e){"stylesheet"===e.attr("rel")&&(d[e.attr("href")]=e)}),w.each(o.stylesheets,function(e){d[e]||((a=new f("link",1)).attr({rel:"stylesheet",text:"text/css",href:e}),a.shortEnded=!0,r(a)),delete d[e]}),w.each(d,function(e){e.remove()}),(a=c.getAll("body")[0])&&(n(a,"dir",o.langdir),n(a,"style",o.style),n(a,"vlink",o.visited_color),n(a,"link",o.link_color),n(a,"alink",o.active_color),i.setAttribs(e.getBody(),{style:o.style,dir:o.dir,vLink:o.visited_color,link:o.link_color,aLink:o.active_color})),(a=c.getAll("html")[0])&&(n(a,"lang",o.langcode),n(a,"xml:lang",o.langcode)),s.firstChild||s.remove();var u=p({validate:!1,indent:!0,indent_before:"head,html,body,meta,title,script,link,style",indent_after:"head,html,body,meta,title,script,link,style"}).serialize(c);return u.substring(0,u.indexOf("</body>"))}(l,w.extend(o,t),i.get());i.set(n),e.close()}})})}function A(e){return e.replace(/<\/?[A-Z]+/g,function(e){return e.toLowerCase()})}function s(e,t,n,l){var i,o,r,a,c,s,d,u,m,g,f,p,h,y="",v=e.dom;l.selection||(a=e.getParam("protect"),c=l.content,w.each(a,function(e){c=c.replace(e,function(e){return"\x3c!--mce:protected "+escape(e)+"--\x3e"})}),r=c,"raw"===l.format&&t.get()||l.source_view&&_(e)||(-1!==(i=(r=(r=0!==r.length||l.source_view?r:w.trim(t.get())+"\n"+w.trim(r)+"\n"+w.trim(n.get())).replace(/<(\/?)BODY/gi,"<$1body")).indexOf("<body"))?(i=r.indexOf(">",i),t.set(A(r.substring(0,i+1))),-1===(o=r.indexOf("</body",i))&&(o=r.length),l.content=w.trim(r.substring(i+1,o)),n.set(A(r.substring(o)))):(t.set((h=p="",(g=e).getParam("fullpage_default_xml_pi")&&(p+='<?xml version="1.0" encoding="'+(b(g)||"ISO-8859-1")+'" ?>\n'),p+=g.getParam("fullpage_default_doctype","<!DOCTYPE html>"),p+="\n<html>\n<head>\n",(f=g.getParam("fullpage_default_title"))&&(p+="<title>"+f+"\n"),(f=b(g))&&(p+='\n'),(f=x(g))&&(h+="font-family: "+f+";"),(f=k(g))&&(h+="font-size: "+f+";"),(f=g.getParam("fullpage_default_text_color"))&&(h+="color: "+f+";"),p+="\n\n")),n.set("\n\n")),s=C(e,t.get()),P(s.getAll("style"),function(e){e.firstChild&&(y+=e.firstChild.value)}),(d=s.getAll("body")[0])&&v.setAttribs(e.getBody(),{style:d.attr("style")||"",dir:d.attr("dir")||"",vLink:d.attr("vlink")||"",link:d.attr("link")||"",aLink:d.attr("alink")||""}),v.remove("fullpage_styles"),u=e.getDoc().getElementsByTagName("head")[0],y&&v.add(u,"style",{id:"fullpage_styles"}).appendChild(document.createTextNode(y)),m={},w.each(u.getElementsByTagName("link"),function(e){"stylesheet"===e.rel&&e.getAttribute("data-mce-fullpage")&&(m[e.href]=e)}),w.each(s.getAll("link"),function(e){var t=e.attr("href");if(!t)return!0;m[t]||"stylesheet"!==e.attr("rel")||v.add(u,"link",{rel:"stylesheet",text:"text/css",href:t,"data-mce-fullpage":"1"}),delete m[t]}),w.each(m,function(e){e.parentNode.removeChild(e)})))}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),g=function(){return(g=Object.assign||function(e){for(var t,n=1,l=arguments.length;n/g,function(e,t){return unescape(t)}))})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/fullscreen/plugin.min.js b/apps/st/public/tinymce/plugins/fullscreen/plugin.min.js new file mode 100644 index 000000000..81b4d3649 --- /dev/null +++ b/apps/st/public/tinymce/plugins/fullscreen/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function a(e){var n=e;return{get:function(){return n},set:function(e){n=e}}}function e(t){return function(e){return r=typeof(n=e),(null===n?"null":"object"==r&&(Array.prototype.isPrototypeOf(n)||n.constructor&&"Array"===n.constructor.name)?"array":"object"==r&&(String.prototype.isPrototypeOf(n)||n.constructor&&"String"===n.constructor.name)?"string":r)===t;var n,r}}function n(n){return function(e){return typeof e===n}}function c(e){return!(null==e)}function s(){}function y(e){return function(){return e}}function r(e){return e}var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=e("string"),d=e("array"),o=n("boolean"),h=n("function"),i=n("number");function u(){return m}var v=y(!1),f=y(!0),m={fold:function(e,n){return e()},isSome:v,isNone:f,getOr:r,getOrThunk:g,getOrDie:function(e){throw new Error(e||"error: getOrDie called on none.")},getOrNull:y(null),getOrUndefined:y(void 0),or:r,orThunk:g,map:u,each:s,bind:u,exists:v,forall:f,filter:function(){return m},toArray:function(){return[]},toString:y("none()")};function g(e){return e()}function p(e){function n(){return r.get().each(e)}var r=a(A.none());return{clear:function(){n(),r.set(A.none())},isSet:function(){return r.get().isSome()},get:function(){return r.get()},set:function(e){n(),r.set(A.some(e))}}}function w(){return p(function(e){return e.unbind()})}function C(e,n){for(var r=e.length,t=new Array(r),o=0;o'+e.name+""}var t,o,n,i,r,s,l,c;return{name:"plugins",title:"Plugins",items:[{type:"htmlpanel",presets:"document",html:[null==e?"":'
'+(s=b((i=w((n=o=e).plugins),void 0===(r=n.getParam("forced_plugins"))?i:m(i,function(e){return!(-1"+(t=o,n=e,function(e,t,n){for(var a=0,o=e.length;a";var t,n}),l=s.length,c=s.join(""),"

"+T.translate(["Plugins installed ({0}):",l])+"

    "+c+"
")+"
",(t=b(m(d,function(e){var t=e.key,n=e.type;return"autocorrect"!==t&&"premium"===n}),function(e){return"
  • "+T.translate(e.name)+"
  • "}).join(""),'

    '+T.translate("Premium plugins:")+"

    ")].join("")}]}}function l(d,y){return function(){var e,t,n,a,o,i,r,s,l,c,u,m=(e=d,t=y,r={name:"shortcuts",title:"Handy Shortcuts",items:[{type:"table",header:["Action","Shortcut"],cells:b(x,function(e){var t=b(e.shortcuts,f).join(" or ");return[e.action,t]})}]},s={name:"keyboardnav",title:"Keyboard Navigation",items:[{type:"htmlpanel",presets:"document",html:"

    Editor UI keyboard navigation

    \n\n

    Activating keyboard navigation

    \n\n

    The sections of the outer UI of the editor - the menubar, toolbar, sidebar and footer - are all keyboard navigable. As such, there are multiple ways to activate keyboard navigation:

    \n
      \n
    • Focus the menubar: Alt + F9 (Windows) or ⌥F9 (MacOS)
    • \n
    • Focus the toolbar: Alt + F10 (Windows) or ⌥F10 (MacOS)
    • \n
    • Focus the footer: Alt + F11 (Windows) or ⌥F11 (MacOS)
    • \n
    \n\n

    Focusing the menubar or toolbar will start keyboard navigation at the first item in the menubar or toolbar, which will be highlighted with a gray background. Focusing the footer will start keyboard navigation at the first item in the element path, which will be highlighted with an underline.

    \n\n

    Moving between UI sections

    \n\n

    When keyboard navigation is active, pressing tab will move the focus to the next major section of the UI, where applicable. These sections are:

    \n
      \n
    • the menubar
    • \n
    • each group of the toolbar
    • \n
    • the sidebar
    • \n
    • the element path in the footer
    • \n
    • the wordcount toggle button in the footer
    • \n
    • the branding link in the footer
    • \n
    • the editor resize handle in the footer
    • \n
    \n\n

    Pressing shift + tab will move backwards through the same sections, except when moving from the footer to the toolbar. Focusing the element path then pressing shift + tab will move focus to the first toolbar group, not the last.

    \n\n

    Moving within UI sections

    \n\n

    Keyboard navigation within UI sections can usually be achieved using the left and right arrow keys. This includes:

    \n
      \n
    • moving between menus in the menubar
    • \n
    • moving between buttons in a toolbar group
    • \n
    • moving between items in the element path
    • \n
    \n\n

    In all these UI sections, keyboard navigation will cycle within the section. For example, focusing the last button in a toolbar group then pressing right arrow will move focus to the first item in the same toolbar group.

    \n\n

    Executing buttons

    \n\n

    To execute a button, navigate the selection to the desired button and hit space or enter.

    \n\n

    Opening, navigating and closing menus

    \n\n

    When focusing a menubar button or a toolbar button with a menu, pressing space, enter or down arrow will open the menu. When the menu opens the first item will be selected. To move up or down the menu, press the up or down arrow key respectively. This is the same for submenus, which can also be opened and closed using the left and right arrow keys.

    \n\n

    To close any active menu, hit the escape key. When a menu is closed the selection will be restored to its previous selection. This also works for closing submenus.

    \n\n

    Context toolbars and menus

    \n\n

    To focus an open context toolbar such as the table context toolbar, press Ctrl + F9 (Windows) or ⌃F9 (MacOS).

    \n\n

    Context toolbar navigation is the same as toolbar navigation, and context menu navigation is the same as standard menu navigation.

    \n\n

    Dialog navigation

    \n\n

    There are two types of dialog UIs in TinyMCE: tabbed dialogs and non-tabbed dialogs.

    \n\n

    When a non-tabbed dialog is opened, the first interactive component in the dialog will be focused. Users can navigate between interactive components by pressing tab. This includes any footer buttons. Navigation will cycle back to the first dialog component if tab is pressed while focusing the last component in the dialog. Pressing shift + tab will navigate backwards.

    \n\n

    When a tabbed dialog is opened, the first button in the tab menu is focused. Pressing tab will navigate to the first interactive component in that tab, and will cycle through the tab\u2019s components, the footer buttons, then back to the tab button. To switch to another tab, focus the tab button for the current tab, then use the arrow keys to cycle through the tab buttons.

    "}]},l=k(e),a=C.majorVersion,o=C.minorVersion,i=0===a.indexOf("@")?"X.X.X":a+"."+o,c={name:"versions",title:"Version",items:[{type:"htmlpanel",html:"

    "+T.translate(["You are using {0}",'TinyMCE '+i+""])+"

    ",presets:"document"}]},u=g(((n={})[r.name]=r,n[s.name]=s,n[l.name]=l,n[c.name]=c,n),t.get()),v.from(e.getParam("help_tabs")).fold(function(){return-1!==(n=(t=w(e=u)).indexOf("versions"))&&(t.splice(n,1),t.push("versions")),{tabs:e,names:t};var e,t,n},function(e){return t=u,n={},a=b(e,function(e){return"string"==typeof e?(A(t,e)&&(n[e]=t[e]),e):(n[e.name]=e).name}),{tabs:n,names:a};var t,n,a})),h=m.tabs,p=function(e){for(var t=[],n=function(e){t.push(e)},a=0;a")}),(t=n).ui.registry.addButton("hr",{icon:"horizontal-rule",tooltip:"Horizontal line",onAction:e}),t.ui.registry.addMenuItem("hr",{icon:"horizontal-rule",text:"Horizontal line",onAction:e})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/image/plugin.min.js b/apps/st/public/tinymce/plugins/image/plugin.min.js new file mode 100644 index 000000000..08380e12d --- /dev/null +++ b/apps/st/public/tinymce/plugins/image/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function e(r){return function(e){return n=typeof(t=e),(null===t?"null":"object"==n&&(Array.prototype.isPrototypeOf(t)||t.constructor&&"Array"===t.constructor.name)?"array":"object"==n&&(String.prototype.isPrototypeOf(t)||t.constructor&&"String"===t.constructor.name)?"string":n)===r;var t,n}}function t(t){return function(e){return typeof e===t}}function D(e){return!(null==e)}function l(){}function a(e){return function(){return e}}function n(e){return e}function r(){return g}var i,o=tinymce.util.Tools.resolve("tinymce.PluginManager"),v=function(){return(v=Object.assign||function(e){for(var t,n=1,r=arguments.length;n'+n+"")):e.insertContent(d(e,t))}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),i="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),o="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),u="January February March April May June July August September October November December".split(" "),g=tinymce.util.Tools.resolve("tinymce.util.Tools");e.add("insertdatetime",function(e){var n,r,t,a,i,o,u,c;function m(e){return r.execCommand("mceInsertDate",!1,e)}(n=e).addCommand("mceInsertDate",function(e,t){p(n,null!=t?t:n.getParam("insertdatetime_dateformat",n.translate("%Y-%m-%d")))}),n.addCommand("mceInsertTime",function(e,t){p(n,null!=t?t:l(n))}),u=s(r=e),t=0<(o=s(i=r)).length?o[0]:l(i),a=t,c={get:function(){return a},set:function(e){a=e}},r.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:function(e){return e===c.get()},fetch:function(e){e(g.map(u,function(e){return{type:"choiceitem",text:d(r,e),value:e}}))},onAction:function(e){m(c.get())},onItemAction:function(e,t){c.set(t),m(t)}}),r.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:function(){return g.map(u,function(e){return{type:"menuitem",text:d(r,e),onAction:(t=e,function(){c.set(t),m(t)})};var t})}})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/legacyoutput/plugin.min.js b/apps/st/public/tinymce/plugins/legacyoutput/plugin.min.js new file mode 100644 index 000000000..d04396083 --- /dev/null +++ b/apps/st/public/tinymce/plugins/legacyoutput/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=tinymce.util.Tools.resolve("tinymce.util.Tools");e.add("legacyoutput",function(e){var s,t;(t=s=e).settings.inline_styles=!1,t.getParam("fontsize_formats")||(t.settings.fontsize_formats="8pt=1 10pt=2 12pt=3 14pt=4 18pt=5 24pt=6 36pt=7"),t.getParam("font_formats")||(t.settings.font_formats="Andale Mono=andale mono,monospace;Arial=arial,helvetica,sans-serif;Arial Black=arial black,sans-serif;Book Antiqua=book antiqua,palatino,serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,palatino,serif;Helvetica=helvetica,arial,sans-serif;Impact=impact,sans-serif;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco,monospace;Times New Roman=times new roman,times,serif;Trebuchet MS=trebuchet ms,geneva,sans-serif;Verdana=verdana,geneva,sans-serif;Webdings=webdings;Wingdings=wingdings,zapf dingbats"),s.on("PreInit",function(){var e=s,t="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table",i=l.explode(e.getParam("font_size_style_values","xx-small,x-small,small,medium,large,x-large,xx-large")),a=e.schema;e.formatter.register({alignleft:{selector:t,attributes:{align:"left"}},aligncenter:{selector:t,attributes:{align:"center"}},alignright:{selector:t,attributes:{align:"right"}},alignjustify:{selector:t,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all",preserve_attributes:["class","style"]},{inline:"strong",remove:"all",preserve_attributes:["class","style"]},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all",preserve_attributes:["class","style"]},{inline:"em",remove:"all",preserve_attributes:["class","style"]},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all",preserve_attributes:["class","style"]},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all",preserve_attributes:["class","style"]},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",toggle:!1,attributes:{face:"%value"}},fontsize:{inline:"font",toggle:!1,attributes:{size:function(e){return String(l.inArray(i,e.value)+1)}}},forecolor:{inline:"font",attributes:{color:"%value"},links:!0,remove_similar:!0,clear_child_styles:!0},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"},links:!0,remove_similar:!0,clear_child_styles:!0}}),l.each("b,i,u,strike".split(","),function(e){a.addValidElements(e+"[*]")}),a.getElementRule("font")||a.addValidElements("font[face|size|color|style]"),l.each(t.split(","),function(e){var t=a.getElementRule(e);t&&(t.attributes.align||(t.attributes.align={},t.attributesOrder.push("align")))})})})}(); \ No newline at end of file diff --git a/apps/st/public/tinymce/plugins/link/plugin.min.js b/apps/st/public/tinymce/plugins/link/plugin.min.js new file mode 100644 index 000000000..15a6d90db --- /dev/null +++ b/apps/st/public/tinymce/plugins/link/plugin.min.js @@ -0,0 +1,9 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + * + * Version: 5.10.9 (2023-11-15) + */ +!function(){"use strict";function t(r){return function(t){return e=typeof(n=t),(null===n?"null":"object"==e&&(Array.prototype.isPrototypeOf(n)||n.constructor&&"Array"===n.constructor.name)?"array":"object"==e&&(String.prototype.isPrototypeOf(n)||n.constructor&&"String"===n.constructor.name)?"string":e)===r;var n,e}}function n(n){return function(t){return typeof t===n}}function h(){}function i(t){return function(){return t}}function e(t){return t}function r(t,n){return t===n}function o(){return v}var u,a=tinymce.util.Tools.resolve("tinymce.PluginManager"),c=tinymce.util.Tools.resolve("tinymce.util.VK"),l=t("string"),d=t("array"),p=function(t){return u===t},f=n("boolean"),s=n("function"),m=i(!1),g=i(!(u=null)),v={fold:function(t,n){return t()},isSome:m,isNone:g,getOr:e,getOrThunk:y,getOrDie:function(t){throw new Error(t||"error: getOrDie called on none.")},getOrNull:i(null),getOrUndefined:i(void 0),or:e,orThunk:y,map:o,each:h,bind:o,exists:m,forall:g,filter:function(){return v},toArray:function(){return[]},toString:i("none()")};function y(t){return t()}function k(t,o,i){return function(t){for(var n,e=0,r=t.length;e=e.childNodes.length?n.data.length:0}:n.previousSibling&&Me(n.previousSibling)?{container:n.previousSibling,offset:n.previousSibling.data.length}:n.nextSibling&&Me(n.nextSibling)?{container:n.nextSibling,offset:0}:{container:e,offset:t}}function We(e){var t=e.cloneRange(),n=Ve(e.startContainer,e.startOffset);t.setStart(n.container,n.offset);var r=Ve(e.endContainer,e.endOffset);return t.setEnd(r.container,r.offset),t}function Qe(e,t){var n=t||e.selection.getStart(!0);return e.dom.getParent(n,"OL,UL,DL",qt(e,n))}function Xe(e){var t,n,r=e.selection.getSelectedBlocks();return L((t=e,n=Mt.map(r,function(e){return t.dom.getParent(e,"li,dd,dt",qt(t,e))||e}),Xt.unique(n)),Ft)}function qe(e,t){return w(e.dom.getParents(t,"ol,ul",qt(e,t)))}function ze(e,t,n){return void 0===n&&(n=r),e.exists(function(e){return n(e,t)})}function Ye(e,t,n){return e.isSome()&&t.isSome()?E.some(n(e.getOrDie(),t.getOrDie())):E.none()}function Ze(e,t,n){return e.fire("ListMutation",{action:t,element:n})}function Ge(e,t){we(e.item,t.list)}function Je(e,t){for(var n=0;ne.length?tt:et)(r,e,t)},[])).map(function(e){return e.list}).toArray()}function ft(e,t){b(L(e,it),function(e){return function(e){switch(t){case"Indent":e.depth++;break;case"Outdent":e.depth--;break;case"Flatten":e.depth=0}e.dirty=!0}(e),0})}function dt(e,t){var n,r,o,i,a,s,u,c,f,d,l,m,p,g=C((i=qe(o=r=e,o.selection.getStart()),a=L(o.selection.getSelectedBlocks(),_t),s=i.toArray().concat(a),zt(r,s)),me.fromDom),v=C(L(Xe(e),Ht),me.fromDom),h=!1;return(g.length||v.length)&&(n=e.selection.getBookmark(),c=t,b((f=g,p=C(Xe(u=e),me.fromDom),d=Ye(N(p,y(rt)),N(D(p),y(rt)),function(e,t){return{start:e,end:t}}),l=!1,m={get:function(){return l},set:function(e){l=e}},C(f,function(e){return{sourceList:e,entries:Yt(0,d,m,e)}})),function(e){ft(e.entries,c);var t,n,r=(t=u,T(function(e,t){if(0===e.length)return[];for(var n=t(e[0]),r=[],o=[],i=0,a=e.length;i"):"application/x-shockwave-flash"===n.sourcemime?(u='',n.poster&&(u+=''),u+""):-1!==n.sourcemime.indexOf("audio")?(s=n,m?m(s):'"):"script"===n.type?'