こんにちは。to Cサービスのカスタマーサポートをしてます平湯(ひらゆ)です。
GAS(Google App Script)・HTML・CSSを使って、ひとつの仕組みを簡便にした事例を紹介します。
やったことをざっくりいうと、「GASでHTMLを書いてWebページを作り、Webページに入力された情報をスプレッドシートに自動で転記できるやつを作った」です。
特にコーディングされてないので、綺麗なコードではないです。(指摘ぜひ欲しい)
誰でもやろうと思えば、今ある仕組みを効率的にやれるようになるんだよ、ということを伝えたくて書きました。強いCSチームづくりをしている方の参考になればうれしみ。
何を効率化したのか?
「毎朝の業務チェック」です。恥ずかしながら、何度か対応漏れが発生したり、作業量に対するリソースを見誤ったりしていたので、朝礼で業務チェックを行うようになりました。
具体的には、
- 項目ごとに「対応に漏れはないか?」と「作業量は処理可能な量か?」という視点で状況を共有する。(問題ありそうなら即リカバリ)
- チェックした記録をつける。
これを簡単にしました。
なんでやったの?
シンプルにスプレへの記録がめんどくさかったです。チェック項目は頻度がまばらで、一項目ずつ◯をつけなきゃいけない。しかもその前に、その日の日付を探さなきゃいけない。
業務管理はものすごく重要なんですが、何かを生み出すものじゃない(リスク低減はほぼそうだと思ってる)ので、モチベーションが非常に保ちづらいんですよね。(コンプラマンの皆さんすみません)なので、風化させないために、管理系のタスクはめちゃくちゃ簡便にしなきゃいけないと考えてます。
あと、自分のスキルを磨きたかったからです。自分のリソースで効率化できるとかなりストレスがないです。だって頼まなくてもいいし、自分のタイミングでやれるので気が楽です。いろんなことができるようになると仕事がやりやすいなと思って、やってみました。
HTML・CSSから始めたのは、業務効率化の観点とサポートページを自分でいじりたいと思ったからです。
テーマにしたこと
見やすくて操作しやすい画面にすることをテーマにしました。新しく使う人でも恐れずに操作できるものにしたかったので、今回は見た目を意識しました。あんま手を付けてこなかったCSSにチャレンジした感じです。
完成したもの
これがTOP画面です。確認した項目にチェックを入れていき、「記録する」ボタンを押すと、
~~(省略)~~
↓
記録完了を伝えるサンクスページにいきます。「記録シートにいく」ボタンを押すと、
↓
スプレのチェックシートにいきます。
当日の日付(2021/5/11)に、さっきのページでチェックした項目がスプレの項目で「◯」と記録されました。やったね。
コード
スクリプトは4つあります。①opsRecord.gs、②index.html、③result.html、④css.html です。
①opsRecord.gsで、webページの表示、webページに入力された情報の取得、スプレへの転記などを行います。②index.htmlは項目をチェックする画面のhtml、③result.htmlは作業完了画面のhtmlです。④css.htmlは文字通りCSSで、②と③の画面のスタイルを決めます。
コードとざっくり説明です。(細かいので気になる方はどうぞ)
実際はチェック対象の項目がもっと多いのですが、コードが長くなるので一部割愛してます。
doGet
:htmlを表示させるおまじない。ブラウザのタブに表示されるタイトルとファビコンもここで設定してる。doPost
:webページで入力された内容をスプレに転記する。以下の流れ。- webページに入力された内容を取得して配列に入れる
- 今日の日付と一致するスプレの日付があるレコードを見つける
- webページに入力された内容とスプレのチェック項目が一致したら、そのレコードに丸をつける
- 記録完了ページへ遷移する
recordDay
:次に説明するisBusinessDay
を使って、営業日の場合は当日の日付、営業日じゃなかったら「休」とスプレに記録する。トリガーを設定して日次で実行してる。isBusinessDay
:土日祝日を判定する。
function doGet() {
const htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
htmlOutput
.setTitle('CS業務管理チェック')
.setFaviconUrl('https://drive.google.com/uc?id=xxxxxxxxxx&.png');
return htmlOutput;
}
function doPost(e){
var ss = SpreadsheetApp.openById('xxxxxxxxxx').getSheetByName('xxxxxx');
var lastRow = ss.getLastRow();
var dateList = ss.getRange(2,1,lastRow,1).getValues();
var checkList = ss.getRange(1,2,1,2).getValues();
var today = new Date();
var today = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy-MM-dd');
var cl1 = e.parameter.checklist1;
var cl2 = e.parameter.checklist2;
var answerList = [cl1, cl2];
for(var i=0;i<dateList.length;i++){
if(dateList[i][0]=='休'){
continue;
}
else if(Utilities.formatDate(dateList[i][0], 'Asia/Tokyo', 'yyyy-MM-dd') == today){
if(answerList.includes(checkList[0][0]) == true){
ss.getRange(i+2,2).setValue("◯");
}
if(answerList.includes(checkList[0][1]) == true){
ss.getRange(i+2,3).setValue("◯");
break;
}
}
var html = HtmlService.createTemplateFromFile("result").evaluate();
html
.setTitle('CS業務管理チェック')
.setFaviconUrl('https://drive.google.com/uc?id=xxxxxxxxxx&.png');
return html;
}
}
function recordDay() { //営業日の場合に当日の日付を記録する関数
var ss = SpreadsheetApp.openById('xxxxxxxxxx').getSheetByName('xxxxxx');
const AValues = ss.getRange('A:A').getValues();
const LastRow = AValues.filter(String).length;
var pasteDestination = ss.getRange(LastRow+1,1);
var today = new Date();
if(isBusinessDay(today)==true){
var today = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy-MM-dd');
pasteDestination.setValue(today);
}
else{
pasteDestination.setValue("休");
}
}
function isBusinessDay(date){ //土日祝日を判定する関数
if (date.getDay() == 0 || date.getDay() == 6) {
return false;
}
const calJa = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com');
if(calJa.getEventsForDay(date).length > 0){
return false;
}
return true;
}
- form要素に実行するアプリケーションを設定して、入力内容を取得できるようにする。
- チェック項目ごとに、項目名・頻度・リンク先を設定。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
<link rel="stylesheet" href="style.css">
<title>CS業務管理チェック</title>
</head>
<body>
<header>
<h1>CSチーム:業務管理チェック</h1>
</header>
<form class="" action="xxx(実行するアプリケーションのURLを入れる)" method="post">
<div class="container">
<div class="list">
<input type="checkbox" name="checklist1" value="1. チケット対応" id="ticket"> <label for="ticket"><p class="text-everyday">1. チケット対応 <span>[毎営業日]</span> <a target="_blank" href="xxxxxxxx" class="link"> zendesk Explore</a> </p></label>
</div>
<div class="list">
<input type="checkbox" name="checklist2" value="2. KYC対応" id="kyc" > <label for="kyc"><p class="text-everyday">2. KYC対応 <span>[毎営業日]</span> <a target="_blank" href="xxxxxxxx" class="link">kyc dashboard</a></p></label>
</div>
<input class="kiroku" type="submit" value="記録する">
</div>
</form>
</body>
</html>
- 記録が完了したことを伝える。
- チェックシート(スプレ)のリンク先を掲出する。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
<link rel="stylesheet" href="style.css">
<title>記録完了ページ</title>
</head>
<body>
<header>
<h1>CSチーム:業務管理チェック</h1>
</header>
<div class="block">
<h1>記録が完了しました!</h1>
</div>
<div class="record">
<a href="xxxxxxx" target="_blank" >記録シートにいく</a>
</div>
</body>
</html>
header
:タイトルを大きく見せるようにした。.list input
:transform: scale(2,2)
でチェックボックスのサイズを大きくした。.kiroku
、.record
、.record a
:なるべく大きいボタンにした。.kiroku:hover
、.record a:hover
:カーソルあわせたときに変わる色を指定。.text-everyday span
:毎営業日確認するものにハイライトを付けた。- 他いろいろ
<style>
body {
font-family:monospace;
}
header {
font-size: 2vw;
background-color: #EAF6FD;
max-width: 100%;
max-height: 150px;
margin: 0 auto;
padding: 50px;
}
.container {
display: flex;
flex-direction: column;
margin-top: 100px;
margin:10%;
padding:0;
}
.list {
display: flex;
font-size: 24px;
height: 64px;
min-width: 24px;
padding: 8px 16px 0 0;
}
.link {
font-size: 20px;
color: blue;
line-height: 8px;
margin-top: 8px;
}
.list span {
font-size: 20px;
}
.text-everyday span {
background: linear-gradient(transparent 40%, gold 60%);
}
.list input {
transform: scale(2,2);
margin: 32px;
}
.kiroku {
display: inline-block;
text-align: center;
background-color: #00A5DD;
color: white;
border-radius: 24px;
max-width: 320px;
margin: 72px;
padding:24px;
font-size:24px;
text-overflow: ellipsis;
white-space: nowrap;
}
.kiroku:hover {
background-color: #0090AA;
color: white;
border-radius: 24px;
}
/サンクスページ/
.block {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 112px;
margin: 10px auto;
}
.record {
display: inline-block;
text-align: center;
width: 100%;
height: 32px;
margin: 10px auto;
}
.record a {
background: #00A5DD;
text-decoration:none;
color: white;
border-radius: 24px;
padding:24px;
font-size:20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.record a:hover {
background-color: #0090AA;
border-radius: 24px;
}
</style>
学びとか工夫したこと
- GASではCSSを読み込むのにひと工夫が必要
- ファビコン入れられるよ
- 文字列型と日付型を同じカラムにしたので苦労した
- チェックボックスを同じnameにするとうまくいかない
- labelタグでチェックしやすく
- デプロイが2回必要だった
- CSSはぜんぶ学び
✏️GASではCSSを読み込むのにひと工夫が必要
GASでHTMLファイルとCSSファイルを分けるときは、headタグにcssを読み込むための呪文を入れます。
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
こいつです。
headタグはこんな感じになります。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
<title>CS業務チェック</title>
</head>
この辺りは、タカハシ先生のwebサイトで勉強しました。非常にわかりやすく、「やってみよう形式」になってるので、一回バーっと体験してみることをオススメします。
✏️ファビコン入れられるよ
Google driveのフォルダに画像を入れて、 doGet
でファイルidを入力すればファビコンを出せます。webサイトっぽさマシマシです。素敵😍
.setFaviconUrl('https://drive.google.com/uc?id=xxxxxxxxxx&.png');
この部分ですね。「xxx…」の部分にファイルidを入れます。
function doGet() {
const htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
htmlOutput
.setTitle('CS業務管理チェック')
.setFaviconUrl('https://drive.google.com/uc?id=xxxxxxxxxx&.png');
return htmlOutput;
}
これもタカハシ先生のwebサイトで知見を得ました。
✏️文字列型と日付型を同じカラムにしたので苦労した
チェックシート(スプレ)には、日付のカラムがあって、営業日の日付と休日には「休」が日次で自動記録されていきます。で、当日の日付を探してチェックシートに「◯」を記録していきます。
日付のカラムに文字列型と日付型を一緒にしないほうがいいと思うんですけど、チェックシートでその日が休みであることを表現したかったので無理やりいきました。
continue
を使って「休」であれば処理をスキップするようにしました。何回もエラーになって原因探るのに割と時間使ったので書いておきます 📝
for(var i=0;i<dateList.length;i++){
if(dateList[i][0]=='休'){
continue;
}
else if(Utilities.formatDate(dateList[i][0], 'Asia/Tokyo', 'yyyy-MM-dd') == today){
if(…
✏️チェックボックスを同じnameにするとうまくいかない
form要素の input
をチェック項目ごとに同じnameにしてしまうと、先頭の情報しか取得できませんでした。ググると同じnameでも取得できるような記述があったのですが、うまくいきませんでした。 name="checklist1"
、name="checklist2"
のように項目ごとに別の名前をつけてます。
<div class="list">
<input type="checkbox" name="checklist1" value="1. チケット対応" id="ticket"> <label for="ticket"><p class="text-everyday">1. チケット対応 <span>[毎営業日]</span> <a target="_blank" href="xxxxxxxx" class="link"> zendesk Explore</a> </p></label>
</div>
<div class="list">
<input type="checkbox" name="checklist2" value="2. KYC対応" id="kyc" > <label for="kyc"><p class="text-everyday">2. KYC対応 <span>[毎営業日]</span> <a target="_blank" href="xxxxxxxx" class="link">kyc dashboard</a></p></label>
</div>
✏️labelタグでチェックしやすく
label
を使って、チェックボックス(「□」)だけじゃなく文言をクリックしてもチェックボックスが反応するようになってます。こんな小技あるんですね、便利。操作しやすさを意識したので細かいとこもやってみました。
あと target="_blank"
で新しいタブで開くことができます。これも細かい小技。
<label for="ticket">
<p class="text-everyday">1. チケット対応 <span>[毎営業日]</span>
<a target="_blank" href="xxxxxxxx" class="link"> zendesk Explore
</a>
</p>
</label>
✏️デプロイが2回必要だった
これも気づくのに結構時間がかかったので書いておきます。
form要素の action
で opsRecord.gs のスクリプトを実行しているので、デプロイしたら最新のアプリケーションのURLを入れる必要があります。
なので、なにか修正を入れたときは、デプロイ →form要素の action
のURLを修正 →再度デプロイ、をしないと修正が反映されません。
<form class="" action="xxx(実行するアプリケーションのURLを入れる)" method="post">
✏️CSSはぜんぶ学び
業務っぽさをなるべく排したかったです。だるいって思い始めたら続かないので操作性もそうだけど見栄えも綺麗にしたかった。
ヘッダーのタイトルやボタン・チェックボックスを大きくするCSSを書いたのもそういう背景からです。
CSSを設定しなかったバージョンを見ると…業務感がすごいですね。チェックボックスもボタンもリンク先も押しづらそう🤦🏼♂️
今回記事にしたwebページを社内のデザイナー(osanai)に見てもらって、もっと良い案のレビューをもらえたので近々更新します。
イメージはこんな感じ。素敵すぎる見栄え!やるぞ!
感想とか
CSSって奥が深いんですね。漠然とHTMLが重要と思ってたんですけど、見た目はほぼCSSなんだなっていう学びを得ました。
今回の経験でやれることの範囲が広がりました。普段の業務のなかで、「あれ、これならGASでやれるな」とか「こいつはwebページ作って簡易的な管理画面として使えたら早くできるな」とかアンテナが前より立つようになりました。
自分の気持ちはいつでもこれです。
おしまい