Spaces:
Sleeping
Sleeping
| library(shiny) | |
| library(shinydashboard) | |
| library(dplyr) | |
| library(readr) | |
| library(DT) | |
| # ===== データ読み込み(複数のファイル名・エンコーディング対応) ===== | |
| load_data <- function() { | |
| # 優先順位で読み込みを試行するファイル名リスト | |
| file_candidates <- c( | |
| "名刺データサンプル.csv", | |
| "EightData_s.csv", | |
| "EightData.csv", | |
| "meishi.csv", | |
| "data.csv" | |
| ) | |
| encodings <- c("UTF-8", "Shift_JIS", "CP932") | |
| for (file_name in file_candidates) { | |
| if (file.exists(file_name)) { | |
| message(paste("📂 ファイル発見:", file_name)) | |
| for (enc in encodings) { | |
| tryCatch({ | |
| df <- read_csv(file_name, locale = locale(encoding = enc), show_col_types = FALSE) | |
| message(paste("✅ 読み込み成功:", file_name, "エンコーディング:", enc)) | |
| return(df) | |
| }, error = function(e) { | |
| message(paste("❌", enc, "で失敗")) | |
| }) | |
| } | |
| } | |
| } | |
| # ファイルが見つからない場合はサンプルデータを生成 | |
| message("⚠️ CSVファイルが見つかりません。サンプルデータを生成します...") | |
| return(create_sample_data()) | |
| } | |
| # サンプルデータ生成関数 | |
| create_sample_data <- function() { | |
| companies <- c("株式会社ABC", "株式会社XYZ", "テクノソリューションズ", "グローバル商事", "デジタルネットワーク") | |
| departments <- c("営業部", "開発部", "マーケティング部", "人事部", "経理部") | |
| positions <- c("部長", "課長", "係長", "主任", "担当") | |
| last_names <- c("田中", "鈴木", "佐藤", "高橋", "山田", "伊藤", "渡辺", "中村", "小林", "加藤") | |
| first_names <- c("太郎", "花子", "一郎", "二郎", "三郎", "さくら", "翔太", "美咲", "健太", "優子") | |
| n <- 20 | |
| data.frame( | |
| 会社名 = sample(companies, n, replace = TRUE), | |
| 部署名 = sample(departments, n, replace = TRUE), | |
| 役職 = sample(positions, n, replace = TRUE), | |
| 姓 = sample(last_names, n, replace = TRUE), | |
| 名 = sample(first_names, n, replace = TRUE), | |
| `e-mail` = paste0(tolower(sample(last_names, n, replace = TRUE)), "@example.com"), | |
| 郵便番号 = sprintf("%03d-%04d", sample(100:999, n, replace = TRUE), sample(1000:9999, n, replace = TRUE)), | |
| 住所 = paste0("東京都", sample(c("千代田区", "港区", "新宿区", "渋谷区", "中央区"), n, replace = TRUE)), | |
| TEL会社 = sprintf("03-%04d-%04d", sample(1000:9999, n, replace = TRUE), sample(1000:9999, n, replace = TRUE)), | |
| TEL部門 = NA_character_, | |
| TEL直通 = NA_character_, | |
| Fax = NA_character_, | |
| 携帯電話 = sprintf("090-%04d-%04d", sample(1000:9999, n, replace = TRUE), sample(1000:9999, n, replace = TRUE)), | |
| URL = paste0("https://", tolower(sample(companies, n, replace = TRUE)), ".co.jp") |> gsub("株式会社", "", x = _), | |
| 名刺交換日 = format(Sys.Date() - sample(1:365, n, replace = TRUE), "%Y/%m/%d"), | |
| stringsAsFactors = FALSE, | |
| check.names = FALSE | |
| ) | |
| } | |
| # データ読み込み | |
| df <- load_data() | |
| # 列名の確認と調整 | |
| expected_cols <- c("会社名", "部署名", "役職", "姓", "名", "e-mail", "郵便番号", "住所", | |
| "TEL会社", "TEL部門", "TEL直通", "Fax", "携帯電話", "URL", "名刺交換日") | |
| # 存在する列だけ使用 | |
| available_cols <- intersect(expected_cols, colnames(df)) | |
| message(paste("📊 データ:", nrow(df), "行,", ncol(df), "列")) | |
| # ===== UI ===== | |
| ui <- dashboardPage( | |
| skin = "blue", | |
| dashboardHeader(title = "📇 名刺データベース"), | |
| dashboardSidebar( | |
| sidebarMenu( | |
| menuItem("🔍 検索・閲覧", tabName = "search", icon = icon("search")), | |
| menuItem("📊 統計情報", tabName = "stats", icon = icon("bar-chart")), | |
| menuItem("📥 データ管理", tabName = "download", icon = icon("download")) | |
| ), | |
| hr(), | |
| h4("検索条件", style = "padding-left: 15px;"), | |
| textInput("keyword", "キーワード検索", placeholder = "名前、会社名、メールなど"), | |
| selectInput("filter_company", "会社名で絞り込み", | |
| choices = c("すべて" = "", sort(unique(na.omit(df$会社名))))), | |
| selectInput("filter_dept", "部署名で絞り込み", | |
| choices = c("すべて" = "", sort(unique(na.omit(df$部署名))))), | |
| selectInput("filter_position", "役職で絞り込み", | |
| choices = c("すべて" = "", sort(unique(na.omit(df$役職))))), | |
| actionButton("search_btn", "🔎 検索", class = "btn-primary btn-block"), | |
| actionButton("reset_btn", "🔄 リセット", class = "btn-default btn-block"), | |
| hr(), | |
| h5(textOutput("result_count"), style = "padding-left: 15px; font-weight: bold;") | |
| ), | |
| dashboardBody( | |
| tags$head( | |
| tags$style(HTML(" | |
| .content-wrapper { background-color: #f4f6f9; } | |
| .box { border-radius: 5px; } | |
| .dataTables_filter { display: none; } | |
| ")) | |
| ), | |
| tabItems( | |
| # 検索・閲覧タブ | |
| tabItem( | |
| tabName = "search", | |
| fluidRow( | |
| box( | |
| title = "検索結果", width = 12, status = "primary", solidHeader = TRUE, | |
| DT::dataTableOutput("data_table"), | |
| br(), | |
| downloadButton("download_csv", "📥 検索結果をCSVでダウンロード", class = "btn-success") | |
| ) | |
| ) | |
| ), | |
| # 統計情報タブ | |
| tabItem( | |
| tabName = "stats", | |
| fluidRow( | |
| infoBox("総レコード数", nrow(df), icon = icon("database"), color = "blue", width = 4), | |
| infoBox("企業数", length(unique(na.omit(df$会社名))), icon = icon("building"), color = "green", width = 4), | |
| infoBox("部署数", length(unique(na.omit(df$部署名))), icon = icon("sitemap"), color = "purple", width = 4) | |
| ), | |
| fluidRow( | |
| box(title = "会社別レコード数 (Top 15)", width = 6, status = "info", solidHeader = TRUE, | |
| tableOutput("company_stats")), | |
| box(title = "部署別レコード数 (Top 15)", width = 6, status = "info", solidHeader = TRUE, | |
| tableOutput("department_stats")) | |
| ), | |
| fluidRow( | |
| box(title = "役職別レコード数", width = 6, status = "warning", solidHeader = TRUE, | |
| tableOutput("position_stats")), | |
| box(title = "名刺交換日", width = 6, status = "warning", solidHeader = TRUE, | |
| tableOutput("date_stats")) | |
| ) | |
| ), | |
| # データ管理タブ | |
| tabItem( | |
| tabName = "download", | |
| fluidRow( | |
| box( | |
| title = "データベース全体のダウンロード", width = 12, status = "success", solidHeader = TRUE, | |
| p("現在のデータベース全体をCSV形式でダウンロードできます。"), | |
| downloadButton("download_all_csv", "📥 全データをCSVでダウンロード", class = "btn-primary btn-lg"), | |
| hr(), | |
| h4("データベース情報"), | |
| p(paste("総レコード数:", nrow(df))), | |
| p(paste("カラム数:", ncol(df))), | |
| p(paste("カラム:", paste(colnames(df), collapse = ", "))) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| # ===== Server ===== | |
| server <- function(input, output, session) { | |
| # リアクティブ: フィルタリングされたデータ | |
| filtered_data <- reactive({ | |
| result <- df | |
| # キーワード検索(複数列対象) | |
| if (!is.null(input$keyword) && input$keyword != "") { | |
| keyword <- tolower(input$keyword) | |
| search_cols <- c("会社名", "部署名", "役職", "姓", "名", "e-mail", "住所") | |
| search_cols <- intersect(search_cols, colnames(result)) | |
| result <- result %>% | |
| filter( | |
| Reduce(`|`, lapply(search_cols, function(col) { | |
| grepl(keyword, tolower(as.character(.data[[col]])), fixed = TRUE) | |
| })) | |
| ) | |
| } | |
| # 会社名フィルタ | |
| if (!is.null(input$filter_company) && input$filter_company != "") { | |
| result <- result %>% filter(会社名 == input$filter_company) | |
| } | |
| # 部署名フィルタ | |
| if (!is.null(input$filter_dept) && input$filter_dept != "") { | |
| result <- result %>% filter(部署名 == input$filter_dept) | |
| } | |
| # 役職フィルタ | |
| if (!is.null(input$filter_position) && input$filter_position != "") { | |
| result <- result %>% filter(役職 == input$filter_position) | |
| } | |
| result | |
| }) | |
| # リセットボタン | |
| observeEvent(input$reset_btn, { | |
| updateTextInput(session, "keyword", value = "") | |
| updateSelectInput(session, "filter_company", selected = "") | |
| updateSelectInput(session, "filter_dept", selected = "") | |
| updateSelectInput(session, "filter_position", selected = "") | |
| }) | |
| # データテーブル表示 | |
| output$data_table <- DT::renderDataTable({ | |
| display_cols <- c("会社名", "部署名", "役職", "姓", "名", "e-mail", "携帯電話", "名刺交換日") | |
| display_cols <- intersect(display_cols, colnames(filtered_data())) | |
| DT::datatable( | |
| filtered_data()[, display_cols, drop = FALSE], | |
| options = list( | |
| pageLength = 15, | |
| lengthMenu = c(10, 15, 25, 50, 100), | |
| scrollX = TRUE, | |
| language = list( | |
| lengthMenu = "_MENU_ 件表示", | |
| search = "テーブル内検索:", | |
| info = "_TOTAL_ 件中 _START_ ~ _END_ 件を表示", | |
| paginate = list(previous = "前へ", `next` = "次へ") | |
| ) | |
| ), | |
| filter = "top", | |
| rownames = FALSE | |
| ) | |
| }) | |
| # 検索結果の件数表示 | |
| output$result_count <- renderText({ | |
| paste0("検索結果: ", nrow(filtered_data()), " 件") | |
| }) | |
| # CSV ダウンロード(検索結果) | |
| output$download_csv <- downloadHandler( | |
| filename = function() { | |
| paste0("meishi_search_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".csv") | |
| }, | |
| content = function(file) { | |
| write_csv(filtered_data(), file, na = "") | |
| } | |
| ) | |
| # CSV ダウンロード(全データ) | |
| output$download_all_csv <- downloadHandler( | |
| filename = function() { | |
| paste0("meishi_all_", format(Sys.time(), "%Y%m%d_%H%M%S"), ".csv") | |
| }, | |
| content = function(file) { | |
| write_csv(df, file, na = "") | |
| } | |
| ) | |
| # 統計情報 | |
| output$company_stats <- renderTable({ | |
| df %>% | |
| filter(!is.na(会社名)) %>% | |
| group_by(会社名) %>% | |
| summarise(件数 = n(), .groups = 'drop') %>% | |
| arrange(desc(件数)) %>% | |
| head(15) | |
| }) | |
| output$department_stats <- renderTable({ | |
| df %>% | |
| filter(!is.na(部署名)) %>% | |
| group_by(部署名) %>% | |
| summarise(件数 = n(), .groups = 'drop') %>% | |
| arrange(desc(件数)) %>% | |
| head(15) | |
| }) | |
| output$position_stats <- renderTable({ | |
| df %>% | |
| filter(!is.na(役職)) %>% | |
| group_by(役職) %>% | |
| summarise(件数 = n(), .groups = 'drop') %>% | |
| arrange(desc(件数)) | |
| }) | |
| output$date_stats <- renderTable({ | |
| df %>% | |
| filter(!is.na(名刺交換日)) %>% | |
| group_by(名刺交換日) %>% | |
| summarise(件数 = n(), .groups = 'drop') %>% | |
| arrange(desc(名刺交換日)) %>% | |
| head(15) | |
| }) | |
| } | |
| # アプリを起動 | |
| shinyApp(ui, server) |