nguyenhai266 commited on
Commit
61d1fb3
·
0 Parent(s):

Add initial Spring Boot attendance app scaffold

Browse files

Add project scaffolding for an attendance system: Maven wrapper and pom.xml, .gitignore and .gitattributes, Spring Boot application entrypoint and a PageController with basic routes. Include application.properties, JSP views under WEB-INF/views, static assets (CSS, JS) for UI, and a test class. This sets up the initial structure for development and local builds using the Maven wrapper.

Files changed (32) hide show
  1. .gitattributes +4 -0
  2. .gitignore +33 -0
  3. .mvn/wrapper/maven-wrapper.properties +3 -0
  4. mvnw +295 -0
  5. mvnw.cmd +189 -0
  6. pom.xml +97 -0
  7. src/main/java/com/attendenceSystem/AttendenceSystemApplication.java +13 -0
  8. src/main/java/com/attendenceSystem/controllers/PageController.java +38 -0
  9. src/main/resources/application.properties +12 -0
  10. src/main/resources/static/css/attendance.css +472 -0
  11. src/main/resources/static/css/auth.css +281 -0
  12. src/main/resources/static/css/components.css +536 -0
  13. src/main/resources/static/css/layout.css +316 -0
  14. src/main/resources/static/css/style.css +133 -0
  15. src/main/resources/static/css/theme.css +235 -0
  16. src/main/resources/static/js/main.js +88 -0
  17. src/main/webapp/WEB-INF/views/attendances/homeAttendance.jsp +143 -0
  18. src/main/webapp/WEB-INF/views/auths/forgotPass.jsp +0 -0
  19. src/main/webapp/WEB-INF/views/auths/login.jsp +98 -0
  20. src/main/webapp/WEB-INF/views/auths/register.jsp +81 -0
  21. src/main/webapp/WEB-INF/views/dashBoard/employees/dashboard.jsp +173 -0
  22. src/main/webapp/WEB-INF/views/home.jsp +130 -0
  23. src/main/webapp/WEB-INF/views/landing.jsp +130 -0
  24. src/main/webapp/WEB-INF/views/layouts/base.jsp +32 -0
  25. src/main/webapp/WEB-INF/views/layouts/footer.jsp +13 -0
  26. src/main/webapp/WEB-INF/views/layouts/header.jsp +40 -0
  27. src/main/webapp/WEB-INF/views/layouts/sidebar.jsp +46 -0
  28. src/main/webapp/WEB-INF/views/webapp1/face-attendance.jsp +157 -0
  29. src/main/webapp/WEB-INF/views/webapp1/index.jsp +52 -0
  30. src/main/webapp/WEB-INF/views/webapp1/login.jsp +89 -0
  31. src/main/webapp/WEB-INF/views/webapp1/register.jsp +87 -0
  32. src/test/java/com/attendenceSystem/AttendenceSystemApplicationTests.java +13 -0
.gitattributes ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /mvnw text eol=lf
2
+ *.cmd text eol=crlf
3
+ *.db filter=lfs diff=lfs merge=lfs -text
4
+ *.onnx filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HELP.md
2
+ target/
3
+ .mvn/wrapper/maven-wrapper.jar
4
+ !**/src/main/**/target/
5
+ !**/src/test/**/target/
6
+
7
+ ### STS ###
8
+ .apt_generated
9
+ .classpath
10
+ .factorypath
11
+ .project
12
+ .settings
13
+ .springBeans
14
+ .sts4-cache
15
+
16
+ ### IntelliJ IDEA ###
17
+ .idea
18
+ *.iws
19
+ *.iml
20
+ *.ipr
21
+
22
+ ### NetBeans ###
23
+ /nbproject/private/
24
+ /nbbuild/
25
+ /dist/
26
+ /nbdist/
27
+ /.nb-gradle/
28
+ build/
29
+ !**/src/main/**/build/
30
+ !**/src/test/**/build/
31
+
32
+ ### VS Code ###
33
+ .vscode/
.mvn/wrapper/maven-wrapper.properties ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ wrapperVersion=3.3.4
2
+ distributionType=only-script
3
+ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
mvnw ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # ----------------------------------------------------------------------------
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+ # ----------------------------------------------------------------------------
20
+
21
+ # ----------------------------------------------------------------------------
22
+ # Apache Maven Wrapper startup batch script, version 3.3.4
23
+ #
24
+ # Optional ENV vars
25
+ # -----------------
26
+ # JAVA_HOME - location of a JDK home dir, required when download maven via java source
27
+ # MVNW_REPOURL - repo url base for downloading maven distribution
28
+ # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
29
+ # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
30
+ # ----------------------------------------------------------------------------
31
+
32
+ set -euf
33
+ [ "${MVNW_VERBOSE-}" != debug ] || set -x
34
+
35
+ # OS specific support.
36
+ native_path() { printf %s\\n "$1"; }
37
+ case "$(uname)" in
38
+ CYGWIN* | MINGW*)
39
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
40
+ native_path() { cygpath --path --windows "$1"; }
41
+ ;;
42
+ esac
43
+
44
+ # set JAVACMD and JAVACCMD
45
+ set_java_home() {
46
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
47
+ if [ -n "${JAVA_HOME-}" ]; then
48
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
49
+ # IBM's JDK on AIX uses strange locations for the executables
50
+ JAVACMD="$JAVA_HOME/jre/sh/java"
51
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
52
+ else
53
+ JAVACMD="$JAVA_HOME/bin/java"
54
+ JAVACCMD="$JAVA_HOME/bin/javac"
55
+
56
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
57
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
58
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
59
+ return 1
60
+ fi
61
+ fi
62
+ else
63
+ JAVACMD="$(
64
+ 'set' +e
65
+ 'unset' -f command 2>/dev/null
66
+ 'command' -v java
67
+ )" || :
68
+ JAVACCMD="$(
69
+ 'set' +e
70
+ 'unset' -f command 2>/dev/null
71
+ 'command' -v javac
72
+ )" || :
73
+
74
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
75
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
76
+ return 1
77
+ fi
78
+ fi
79
+ }
80
+
81
+ # hash string like Java String::hashCode
82
+ hash_string() {
83
+ str="${1:-}" h=0
84
+ while [ -n "$str" ]; do
85
+ char="${str%"${str#?}"}"
86
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
87
+ str="${str#?}"
88
+ done
89
+ printf %x\\n $h
90
+ }
91
+
92
+ verbose() { :; }
93
+ [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
94
+
95
+ die() {
96
+ printf %s\\n "$1" >&2
97
+ exit 1
98
+ }
99
+
100
+ trim() {
101
+ # MWRAPPER-139:
102
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
103
+ # Needed for removing poorly interpreted newline sequences when running in more
104
+ # exotic environments such as mingw bash on Windows.
105
+ printf "%s" "${1}" | tr -d '[:space:]'
106
+ }
107
+
108
+ scriptDir="$(dirname "$0")"
109
+ scriptName="$(basename "$0")"
110
+
111
+ # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
112
+ while IFS="=" read -r key value; do
113
+ case "${key-}" in
114
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
115
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
116
+ esac
117
+ done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
118
+ [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
119
+
120
+ case "${distributionUrl##*/}" in
121
+ maven-mvnd-*bin.*)
122
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
123
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
124
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
125
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
126
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
127
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
128
+ *)
129
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
130
+ distributionPlatform=linux-amd64
131
+ ;;
132
+ esac
133
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
134
+ ;;
135
+ maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
136
+ *) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
137
+ esac
138
+
139
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
140
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
141
+ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
142
+ distributionUrlName="${distributionUrl##*/}"
143
+ distributionUrlNameMain="${distributionUrlName%.*}"
144
+ distributionUrlNameMain="${distributionUrlNameMain%-bin}"
145
+ MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
146
+ MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
147
+
148
+ exec_maven() {
149
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
150
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
151
+ }
152
+
153
+ if [ -d "$MAVEN_HOME" ]; then
154
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
155
+ exec_maven "$@"
156
+ fi
157
+
158
+ case "${distributionUrl-}" in
159
+ *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
160
+ *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
161
+ esac
162
+
163
+ # prepare tmp dir
164
+ if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
165
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
166
+ trap clean HUP INT TERM EXIT
167
+ else
168
+ die "cannot create temp dir"
169
+ fi
170
+
171
+ mkdir -p -- "${MAVEN_HOME%/*}"
172
+
173
+ # Download and Install Apache Maven
174
+ verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
175
+ verbose "Downloading from: $distributionUrl"
176
+ verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
177
+
178
+ # select .zip or .tar.gz
179
+ if ! command -v unzip >/dev/null; then
180
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
181
+ distributionUrlName="${distributionUrl##*/}"
182
+ fi
183
+
184
+ # verbose opt
185
+ __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
186
+ [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
187
+
188
+ # normalize http auth
189
+ case "${MVNW_PASSWORD:+has-password}" in
190
+ '') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
191
+ has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
192
+ esac
193
+
194
+ if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
195
+ verbose "Found wget ... using wget"
196
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
197
+ elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
198
+ verbose "Found curl ... using curl"
199
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
200
+ elif set_java_home; then
201
+ verbose "Falling back to use Java to download"
202
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
203
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
204
+ cat >"$javaSource" <<-END
205
+ public class Downloader extends java.net.Authenticator
206
+ {
207
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
208
+ {
209
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
210
+ }
211
+ public static void main( String[] args ) throws Exception
212
+ {
213
+ setDefault( new Downloader() );
214
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
215
+ }
216
+ }
217
+ END
218
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
219
+ verbose " - Compiling Downloader.java ..."
220
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
221
+ verbose " - Running Downloader.java ..."
222
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
223
+ fi
224
+
225
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
226
+ if [ -n "${distributionSha256Sum-}" ]; then
227
+ distributionSha256Result=false
228
+ if [ "$MVN_CMD" = mvnd.sh ]; then
229
+ echo "Checksum validation is not supported for maven-mvnd." >&2
230
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
231
+ exit 1
232
+ elif command -v sha256sum >/dev/null; then
233
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
234
+ distributionSha256Result=true
235
+ fi
236
+ elif command -v shasum >/dev/null; then
237
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
238
+ distributionSha256Result=true
239
+ fi
240
+ else
241
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
242
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
243
+ exit 1
244
+ fi
245
+ if [ $distributionSha256Result = false ]; then
246
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
247
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
248
+ exit 1
249
+ fi
250
+ fi
251
+
252
+ # unzip and move
253
+ if command -v unzip >/dev/null; then
254
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
255
+ else
256
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
257
+ fi
258
+
259
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
260
+ actualDistributionDir=""
261
+
262
+ # First try the expected directory name (for regular distributions)
263
+ if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
264
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
265
+ actualDistributionDir="$distributionUrlNameMain"
266
+ fi
267
+ fi
268
+
269
+ # If not found, search for any directory with the Maven executable (for snapshots)
270
+ if [ -z "$actualDistributionDir" ]; then
271
+ # enable globbing to iterate over items
272
+ set +f
273
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
274
+ if [ -d "$dir" ]; then
275
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
276
+ actualDistributionDir="$(basename "$dir")"
277
+ break
278
+ fi
279
+ fi
280
+ done
281
+ set -f
282
+ fi
283
+
284
+ if [ -z "$actualDistributionDir" ]; then
285
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
286
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
287
+ die "Could not find Maven distribution directory in extracted archive"
288
+ fi
289
+
290
+ verbose "Found extracted Maven distribution directory: $actualDistributionDir"
291
+ printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
292
+ mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
293
+
294
+ clean || :
295
+ exec_maven "$@"
mvnw.cmd ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <# : batch portion
2
+ @REM ----------------------------------------------------------------------------
3
+ @REM Licensed to the Apache Software Foundation (ASF) under one
4
+ @REM or more contributor license agreements. See the NOTICE file
5
+ @REM distributed with this work for additional information
6
+ @REM regarding copyright ownership. The ASF licenses this file
7
+ @REM to you under the Apache License, Version 2.0 (the
8
+ @REM "License"); you may not use this file except in compliance
9
+ @REM with the License. You may obtain a copy of the License at
10
+ @REM
11
+ @REM http://www.apache.org/licenses/LICENSE-2.0
12
+ @REM
13
+ @REM Unless required by applicable law or agreed to in writing,
14
+ @REM software distributed under the License is distributed on an
15
+ @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ @REM KIND, either express or implied. See the License for the
17
+ @REM specific language governing permissions and limitations
18
+ @REM under the License.
19
+ @REM ----------------------------------------------------------------------------
20
+
21
+ @REM ----------------------------------------------------------------------------
22
+ @REM Apache Maven Wrapper startup batch script, version 3.3.4
23
+ @REM
24
+ @REM Optional ENV vars
25
+ @REM MVNW_REPOURL - repo url base for downloading maven distribution
26
+ @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
27
+ @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28
+ @REM ----------------------------------------------------------------------------
29
+
30
+ @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31
+ @SET __MVNW_CMD__=
32
+ @SET __MVNW_ERROR__=
33
+ @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34
+ @SET PSModulePath=
35
+ @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37
+ )
38
+ @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39
+ @SET __MVNW_PSMODULEP_SAVE=
40
+ @SET __MVNW_ARG0_NAME__=
41
+ @SET MVNW_USERNAME=
42
+ @SET MVNW_PASSWORD=
43
+ @IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
44
+ @echo Cannot start maven from wrapper >&2 && exit /b 1
45
+ @GOTO :EOF
46
+ : end batch / begin powershell #>
47
+
48
+ $ErrorActionPreference = "Stop"
49
+ if ($env:MVNW_VERBOSE -eq "true") {
50
+ $VerbosePreference = "Continue"
51
+ }
52
+
53
+ # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54
+ $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55
+ if (!$distributionUrl) {
56
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57
+ }
58
+
59
+ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60
+ "maven-mvnd-*" {
61
+ $USE_MVND = $true
62
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63
+ $MVN_CMD = "mvnd.cmd"
64
+ break
65
+ }
66
+ default {
67
+ $USE_MVND = $false
68
+ $MVN_CMD = $script -replace '^mvnw','mvn'
69
+ break
70
+ }
71
+ }
72
+
73
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
74
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
75
+ if ($env:MVNW_REPOURL) {
76
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
78
+ }
79
+ $distributionUrlName = $distributionUrl -replace '^.*/',''
80
+ $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81
+
82
+ $MAVEN_M2_PATH = "$HOME/.m2"
83
+ if ($env:MAVEN_USER_HOME) {
84
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
85
+ }
86
+
87
+ if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
88
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
89
+ }
90
+
91
+ $MAVEN_WRAPPER_DISTS = $null
92
+ if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
93
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
94
+ } else {
95
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
96
+ }
97
+
98
+ $MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
99
+ $MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
100
+ $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
101
+
102
+ if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
103
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
104
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
105
+ exit $?
106
+ }
107
+
108
+ if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
109
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
110
+ }
111
+
112
+ # prepare tmp dir
113
+ $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
114
+ $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
115
+ $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
116
+ trap {
117
+ if ($TMP_DOWNLOAD_DIR.Exists) {
118
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
119
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
120
+ }
121
+ }
122
+
123
+ New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
124
+
125
+ # Download and Install Apache Maven
126
+ Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
127
+ Write-Verbose "Downloading from: $distributionUrl"
128
+ Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
129
+
130
+ $webclient = New-Object System.Net.WebClient
131
+ if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
132
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
133
+ }
134
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
135
+ $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
136
+
137
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
138
+ $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
139
+ if ($distributionSha256Sum) {
140
+ if ($USE_MVND) {
141
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
142
+ }
143
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
144
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
145
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
146
+ }
147
+ }
148
+
149
+ # unzip and move
150
+ Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
151
+
152
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
153
+ $actualDistributionDir = ""
154
+
155
+ # First try the expected directory name (for regular distributions)
156
+ $expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
157
+ $expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
158
+ if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
159
+ $actualDistributionDir = $distributionUrlNameMain
160
+ }
161
+
162
+ # If not found, search for any directory with the Maven executable (for snapshots)
163
+ if (!$actualDistributionDir) {
164
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
165
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
166
+ if (Test-Path -Path $testPath -PathType Leaf) {
167
+ $actualDistributionDir = $_.Name
168
+ }
169
+ }
170
+ }
171
+
172
+ if (!$actualDistributionDir) {
173
+ Write-Error "Could not find Maven distribution directory in extracted archive"
174
+ }
175
+
176
+ Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
177
+ Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
178
+ try {
179
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
180
+ } catch {
181
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
182
+ Write-Error "fail to move MAVEN_HOME"
183
+ }
184
+ } finally {
185
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
186
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
187
+ }
188
+
189
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
pom.xml ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+ <modelVersion>4.0.0</modelVersion>
5
+ <parent>
6
+ <groupId>org.springframework.boot</groupId>
7
+ <artifactId>spring-boot-starter-parent</artifactId>
8
+ <version>4.0.6</version>
9
+ <relativePath/> <!-- lookup parent from repository -->
10
+ </parent>
11
+ <groupId>com.</groupId>
12
+ <artifactId>attendenceSystem</artifactId>
13
+ <version>0.0.1-SNAPSHOT</version>
14
+ <name/>
15
+ <description/>
16
+ <url/>
17
+ <licenses>
18
+ <license/>
19
+ </licenses>
20
+ <developers>
21
+ <developer/>
22
+ </developers>
23
+ <scm>
24
+ <connection/>
25
+ <developerConnection/>
26
+ <tag/>
27
+ <url/>
28
+ </scm>
29
+ <properties>
30
+ <java.version>21</java.version>
31
+ </properties>
32
+ <dependencies>
33
+ <!-- <dependency>
34
+ <groupId>org.springframework.boot</groupId>
35
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
36
+ </dependency> -->
37
+ <dependency>
38
+ <groupId>org.springframework.boot</groupId>
39
+ <artifactId>spring-boot-starter-restclient</artifactId>
40
+ </dependency>
41
+ <dependency>
42
+ <groupId>org.apache.tomcat.embed</groupId>
43
+ <artifactId>tomcat-embed-jasper</artifactId>
44
+ </dependency>
45
+
46
+ <dependency>
47
+ <groupId>jakarta.servlet.jsp.jstl</groupId>
48
+ <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
49
+ </dependency>
50
+
51
+ <dependency>
52
+ <groupId>org.glassfish.web</groupId>
53
+ <artifactId>jakarta.servlet.jsp.jstl</artifactId>
54
+ </dependency>
55
+ <dependency>
56
+ <groupId>org.springframework.boot</groupId>
57
+ <artifactId>spring-boot-starter-webmvc</artifactId>
58
+ </dependency>
59
+ <dependency>
60
+ <groupId>org.springframework.boot</groupId>
61
+ <artifactId>spring-boot-devtools</artifactId>
62
+ <scope>runtime</scope>
63
+ <optional>true</optional>
64
+ </dependency>
65
+ <!-- <dependency>
66
+ <groupId>com.mysql</groupId>
67
+ <artifactId>mysql-connector-j</artifactId>
68
+ <scope>runtime</scope>
69
+ </dependency> -->
70
+ <!-- <dependency>
71
+ <groupId>org.springframework.boot</groupId>
72
+ <artifactId>spring-boot-starter-data-jpa-test</artifactId>
73
+ <scope>test</scope>
74
+ </dependency> -->
75
+ <dependency>
76
+ <groupId>org.springframework.boot</groupId>
77
+ <artifactId>spring-boot-starter-restclient-test</artifactId>
78
+ <scope>test</scope>
79
+ </dependency>
80
+
81
+ <dependency>
82
+ <groupId>org.springframework.boot</groupId>
83
+ <artifactId>spring-boot-starter-webmvc-test</artifactId>
84
+ <scope>test</scope>
85
+ </dependency>
86
+ </dependencies>
87
+
88
+ <build>
89
+ <plugins>
90
+ <plugin>
91
+ <groupId>org.springframework.boot</groupId>
92
+ <artifactId>spring-boot-maven-plugin</artifactId>
93
+ </plugin>
94
+ </plugins>
95
+ </build>
96
+
97
+ </project>
src/main/java/com/attendenceSystem/AttendenceSystemApplication.java ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.attendenceSystem;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+ @SpringBootApplication
7
+ public class AttendenceSystemApplication {
8
+
9
+ public static void main(String[] args) {
10
+ SpringApplication.run(AttendenceSystemApplication.class, args);
11
+ }
12
+
13
+ }
src/main/java/com/attendenceSystem/controllers/PageController.java ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.attendenceSystem.controllers;
2
+
3
+ import org.springframework.stereotype.Controller;
4
+ import org.springframework.web.bind.annotation.GetMapping;
5
+ import org.springframework.web.bind.annotation.RequestMapping;
6
+ import org.springframework.web.bind.annotation.ResponseBody;
7
+
8
+ @Controller
9
+ @RequestMapping("/")
10
+ public class PageController {
11
+
12
+ @GetMapping("home")
13
+ public String home() {
14
+ return "home";
15
+ }
16
+ @GetMapping("login")
17
+ public String login() {
18
+ return "auths/login";
19
+ }
20
+ @GetMapping("register")
21
+ public String register() {
22
+ return "auths/register";
23
+ }
24
+ @GetMapping("attendance")
25
+ public String homeAttendance() {
26
+ return "attendances/homeAttendance";
27
+ }
28
+ @GetMapping("dashboard")
29
+ public String dashboard() {
30
+ return "dashboard/employees/dashboard";
31
+ }
32
+
33
+ @GetMapping("test")
34
+ @ResponseBody
35
+ public String test() {
36
+ return "OK";
37
+ }
38
+ }
src/main/resources/application.properties ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ spring.application.name=attendenceSystem
2
+
3
+
4
+ # JSP Configuration
5
+ spring.mvc.view.prefix=/WEB-INF/views/
6
+ spring.mvc.view.suffix=.jsp
7
+ # Thymeleaf Configuration
8
+ # spring.thymeleaf.prefix=classpath:/templates/
9
+ # spring.thymeleaf.suffix=.html
10
+ # spring.thymeleaf.cache=false
11
+ # spring.thymeleaf.encoding=UTF-8
12
+ # spring.thymeleaf.mode=HTML
src/main/resources/static/css/attendance.css ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===================================
2
+ RESET
3
+ =================================== */
4
+
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: "Segoe UI", sans-serif;
13
+ background: #050b17;
14
+ color: white;
15
+ min-height: 100vh;
16
+ }
17
+
18
+ /* ===================================
19
+ HEADER
20
+ =================================== */
21
+
22
+ .header {
23
+ height: 80px;
24
+ background: #151d2f;
25
+ border-bottom: 1px solid #23365b;
26
+
27
+ display: flex;
28
+ justify-content: space-between;
29
+ align-items: center;
30
+
31
+ padding: 0 40px;
32
+ }
33
+
34
+ .header-left {
35
+ display: flex;
36
+
37
+ gap: 20px;
38
+ }
39
+
40
+ .header-logo a {
41
+ text-decoration: none;
42
+ color: #3b82f6;
43
+ font-size: 2rem;
44
+ font-weight: 700;
45
+ }
46
+
47
+ .header-left h1 {
48
+ font-size: 1.2rem;
49
+ color: #d8e3f8;
50
+ }
51
+
52
+ .header-time {
53
+ color: #8fa3c7;
54
+ }
55
+
56
+ .header-right {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 16px;
60
+ }
61
+
62
+ .header-clock {
63
+ min-width: 140px;
64
+ height: 70px;
65
+
66
+ display: flex;
67
+ justify-content: center;
68
+ align-items: center;
69
+
70
+ background: #19253d;
71
+ border: 1px solid #29426f;
72
+ border-radius: 16px;
73
+
74
+ color: #ffffff;
75
+ font-size: 1.8rem;
76
+ font-weight: 700;
77
+
78
+ box-shadow:
79
+ 0 0 15px rgba(59,130,246,.15);
80
+ }
81
+
82
+ .header-btn {
83
+ width: 80px;
84
+ height: 40px;
85
+
86
+ border: none;
87
+ border-radius: 16px;
88
+
89
+ cursor: pointer;
90
+
91
+ background: #19253d;
92
+ color: white;
93
+
94
+ font-size: 1.4rem;
95
+
96
+ transition: all .3s;
97
+ }
98
+
99
+ .header-btn:hover {
100
+ transform: translateY(-2px);
101
+ }
102
+
103
+ .theme-toggle:hover {
104
+ background: #243b63;
105
+ }
106
+
107
+ .login-btn {
108
+ background: #3b82f6;
109
+ }
110
+
111
+ .login-btn:hover {
112
+ background: #2563eb;
113
+ }
114
+
115
+ .header-login,
116
+ .header-changeLight {
117
+ border: none;
118
+ background: #1c2841;
119
+ color: white;
120
+
121
+ padding: 12px 20px;
122
+ border-radius: 12px;
123
+ cursor: pointer;
124
+
125
+ transition: 0.3s;
126
+ }
127
+
128
+ .header-login:hover,
129
+ .header-changeLight:hover {
130
+ background: #3b82f6;
131
+ }
132
+
133
+ /* ===================================
134
+ MAIN LAYOUT
135
+ =================================== */
136
+
137
+ .container {
138
+ padding: 30px;
139
+
140
+ display: grid;
141
+ grid-template-columns: 320px 1fr 320px;
142
+ gap: 25px;
143
+ }
144
+
145
+ /* ===================================
146
+ CARD CHUNG
147
+ =================================== */
148
+
149
+ .container-attend-left,
150
+ .container-attend-right,
151
+ .face-guide {
152
+ background: #151d2f;
153
+ border: 1px solid #23365b;
154
+ border-radius: 20px;
155
+ }
156
+
157
+ /* ===================================
158
+ HISTORY
159
+ =================================== */
160
+
161
+ .container-attend-left {
162
+ padding: 25px;
163
+ }
164
+
165
+ .history-title {
166
+ color: #3b82f6;
167
+ margin-bottom: 20px;
168
+ font-size: 1.5rem;
169
+ }
170
+
171
+ .history-list {
172
+ list-style: none;
173
+
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 15px;
177
+ }
178
+
179
+ .history-item {
180
+ background: #1a2741;
181
+
182
+ padding: 15px;
183
+ border-radius: 14px;
184
+
185
+ border: 1px solid #263a61;
186
+
187
+ line-height: 1.6;
188
+ }
189
+
190
+ /* ===================================
191
+ CAMERA
192
+ =================================== */
193
+
194
+ .container-attend-main {
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 25px;
198
+ }
199
+
200
+ .face-scanner {
201
+ position: relative;
202
+
203
+ height: 560px;
204
+
205
+ background: #111b2f;
206
+
207
+ border-radius: 24px;
208
+ border: 2px solid #3b82f6;
209
+
210
+ overflow: hidden;
211
+
212
+ box-shadow:
213
+ 0 0 20px rgba(59,130,246,.3),
214
+ inset 0 0 50px rgba(59,130,246,.05);
215
+ }
216
+
217
+ /* camera */
218
+
219
+ #camera {
220
+ width: 100%;
221
+ height: 100%;
222
+ object-fit: cover;
223
+ }
224
+
225
+ /* khung nhận diện */
226
+
227
+ .scan-overlay {
228
+ position: absolute;
229
+
230
+ width: 340px;
231
+ height: 420px;
232
+
233
+ border: 5px solid #3b82f6;
234
+ border-radius: 25px;
235
+
236
+ top: 50%;
237
+ left: 50%;
238
+
239
+ transform: translate(-50%, -50%);
240
+
241
+ box-shadow:
242
+ 0 0 20px #3b82f6,
243
+ inset 0 0 15px rgba(59,130,246,.5);
244
+ }
245
+
246
+ /* hiệu ứng tia scan */
247
+
248
+ .scan-overlay::after {
249
+ content: "";
250
+
251
+ position: absolute;
252
+
253
+ left: -50px;
254
+ right: -50px;
255
+
256
+ top: 50%;
257
+
258
+ height: 4px;
259
+
260
+ background: rgba(59,130,246,.8);
261
+
262
+ box-shadow: 0 0 20px #3b82f6;
263
+
264
+ animation: scan 3s infinite linear;
265
+ }
266
+
267
+ @keyframes scan {
268
+ 0% {
269
+ top: 10%;
270
+ }
271
+
272
+ 50% {
273
+ top: 90%;
274
+ }
275
+
276
+ 100% {
277
+ top: 10%;
278
+ }
279
+ }
280
+
281
+ /* kết quả */
282
+
283
+ .result-card {
284
+ position: absolute;
285
+
286
+ bottom: 20px;
287
+ left: 50%;
288
+
289
+ transform: translateX(-50%);
290
+
291
+ background: rgba(30,64,175,.8);
292
+
293
+ padding: 14px 28px;
294
+
295
+ border-radius: 30px;
296
+
297
+ font-size: 1rem;
298
+ }
299
+
300
+ /* ===================================
301
+ GUIDE
302
+ =================================== */
303
+
304
+ .face-guide {
305
+ padding: 25px;
306
+ }
307
+
308
+ .face-guide p {
309
+ color: #3b82f6;
310
+ font-size: 1.2rem;
311
+ margin-bottom: 15px;
312
+ font-weight: 600;
313
+ }
314
+
315
+ .face-guide ol {
316
+ padding-left: 20px;
317
+ }
318
+
319
+ .face-guide li {
320
+ margin-bottom: 12px;
321
+ color: #d7dff0;
322
+ }
323
+
324
+ /* ===================================
325
+ STATISTICS
326
+ =================================== */
327
+
328
+ .container-attend-right {
329
+ background: #151d2f;
330
+ border: 1px solid #23365b;
331
+ border-radius: 20px;
332
+
333
+ padding: 25px;
334
+
335
+ display: flex;
336
+ flex-direction: column;
337
+ gap: 18px;
338
+ }
339
+
340
+ .attendance-title {
341
+ color: #3b82f6;
342
+ font-size: 1.5rem;
343
+ font-weight: 600;
344
+
345
+ margin-bottom: 10px;
346
+ }
347
+
348
+ /* Card chung */
349
+
350
+ .stat-card {
351
+ padding: 20px;
352
+ border-radius: 16px;
353
+
354
+ display: flex;
355
+ justify-content: space-between;
356
+ align-items: center;
357
+
358
+ transition: .3s;
359
+ }
360
+
361
+ .stat-card:hover {
362
+ transform: translateY(-2px);
363
+ }
364
+
365
+ /* Text trong card */
366
+
367
+ .stat-card p {
368
+ margin: 0;
369
+ font-size: 1rem;
370
+ font-weight: 500;
371
+ }
372
+
373
+ /* Số liệu */
374
+
375
+ .stat-number {
376
+ font-size: 2rem;
377
+ font-weight: 700;
378
+ }
379
+
380
+ /* Đã chấm công */
381
+
382
+ .success-card {
383
+ background: rgba(0, 255, 170, 0.12);
384
+ border: 1px solid rgba(0, 255, 170, 0.3);
385
+ }
386
+
387
+ .success-card .attendance-present,
388
+ .success-card .stat-number {
389
+ color: #00e29b;
390
+ }
391
+
392
+ /* Đi muộn */
393
+
394
+ .warning-card {
395
+ background: rgba(255, 166, 0, 0.12);
396
+ border: 1px solid rgba(255, 166, 0, 0.3);
397
+ }
398
+
399
+ .warning-card .attendance-late,
400
+ .warning-card .stat-number {
401
+ color: #ffb347;
402
+ }
403
+
404
+ /* Chưa chấm công */
405
+
406
+ .danger-card {
407
+ background: rgba(255, 70, 70, 0.12);
408
+ border: 1px solid rgba(255, 70, 70, 0.3);
409
+ }
410
+
411
+ .danger-card .attendance-absent,
412
+ .danger-card .stat-number {
413
+ color: #ff6161;
414
+ }
415
+
416
+ /* Khối thông tin */
417
+
418
+ .attendance-info {
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: 12px;
422
+
423
+ margin-top: 5px;
424
+ }
425
+
426
+ /* Tổng nhân viên */
427
+
428
+ .attendance-sum {
429
+ padding: 16px;
430
+ border-radius: 14px;
431
+
432
+ background: rgba(59, 130, 246, 0.12);
433
+ border: 1px solid rgba(59, 130, 246, 0.3);
434
+
435
+ color: #dbeafe;
436
+ }
437
+
438
+ /* Giờ làm việc */
439
+
440
+ .attendance-date {
441
+ padding: 16px;
442
+ border-radius: 14px;
443
+
444
+ background: rgba(255, 166, 0, 0.12);
445
+ border: 1px solid rgba(255, 166, 0, 0.3);
446
+
447
+ color: #ffb347;
448
+ }
449
+
450
+ /* Xóa margin mặc định */
451
+
452
+ .attendance-present,
453
+ .attendance-late,
454
+ .attendance-absent,
455
+ .attendance-sum,
456
+ .attendance-date {
457
+ margin: 0;
458
+ }
459
+ /* ===================================
460
+ RESPONSIVE
461
+ =================================== */
462
+
463
+ @media (max-width: 1200px) {
464
+
465
+ .container {
466
+ grid-template-columns: 1fr;
467
+ }
468
+
469
+ .face-scanner {
470
+ height: 450px;
471
+ }
472
+ }
src/main/resources/static/css/auth.css ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ==========================================
2
+ LOGIN PAGE
3
+ ========================================== */
4
+
5
+ .login-section {
6
+ min-height: 100vh;
7
+ padding-top: 30px;
8
+ /* display: flex; */
9
+ align-items: center;
10
+ padding-bottom: 30px;
11
+ background:
12
+ radial-gradient(circle at top left,
13
+ rgba(37,99,235,.18),
14
+ transparent 30%),
15
+
16
+ radial-gradient(circle at bottom right,
17
+ rgba(239,68,68,.12),
18
+ transparent 40%),
19
+
20
+ #070b16;
21
+ }
22
+
23
+ /* ==========================================
24
+ LEFT SIDE
25
+ ========================================== */
26
+
27
+ .welcome-panel {
28
+ color: white;
29
+ padding-right: 60px;
30
+ }
31
+
32
+ .logo-circle {
33
+ width: 80px;
34
+ height: 80px;
35
+
36
+ border-radius: 24px;
37
+
38
+ background: rgba(37,99,235,.15);
39
+
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+
44
+ margin-bottom: 30px;
45
+ }
46
+
47
+ .logo-circle i {
48
+ color: #3b82f6;
49
+ font-size: 42px;
50
+ }
51
+
52
+ .left-title {
53
+ color: white;
54
+
55
+ font-size: 3.8rem;
56
+ font-weight: 700;
57
+
58
+ margin-bottom: 25px;
59
+
60
+ line-height: 1.1;
61
+ }
62
+
63
+ .left-description {
64
+ color: #94a3b8;
65
+
66
+ font-size: 1.1rem;
67
+
68
+ line-height: 1.8;
69
+ }
70
+
71
+ /* ==========================================
72
+ LOGIN CARD
73
+ ========================================== */
74
+
75
+
76
+ .card-header{
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 20px;
80
+ margin-bottom: 20px;
81
+ }
82
+
83
+ .login-card {
84
+ background: #111827;
85
+
86
+ border: 1px solid rgba(59,130,246,.2);
87
+
88
+ border-radius: 24px;
89
+
90
+ padding: 20px;
91
+
92
+ max-width: 480px;
93
+
94
+ box-shadow:
95
+ 0 20px 60px rgba(0,0,0,.35),
96
+ 0 0 0 1px rgba(59,130,246,.05);
97
+ }
98
+
99
+ .card-icon {
100
+ width: 70px;
101
+ height: 70px;
102
+
103
+ border-radius: 18px;
104
+
105
+ background: rgba(16,185,129,.15);
106
+
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+
111
+ margin-bottom: 20px;
112
+ }
113
+
114
+ .card-icon i {
115
+ color: #10b981;
116
+ font-size: 32px;
117
+ }
118
+
119
+ .login-card h2 {
120
+ color: white;
121
+
122
+ font-size: 2rem;
123
+ font-weight: 700;
124
+
125
+ margin-bottom: 25px;
126
+ }
127
+
128
+ /* ==========================================
129
+ FORM
130
+ ========================================== */
131
+
132
+ .form-group {
133
+ margin-bottom: 18px;
134
+ }
135
+
136
+ .form-group label {
137
+ display: block;
138
+
139
+ color: #cbd5e1;
140
+
141
+ font-weight: 500;
142
+
143
+ margin-bottom: 8px;
144
+ }
145
+
146
+ .form-control {
147
+ width: 100%;
148
+
149
+ height: 54px;
150
+
151
+ background: #0f172a;
152
+
153
+ border: 1px solid #334155;
154
+
155
+ border-radius: 14px;
156
+
157
+ color: white;
158
+ }
159
+
160
+ .form-control:focus {
161
+ background: #0f172a;
162
+
163
+ color: white;
164
+
165
+ border-color: #3b82f6;
166
+
167
+ box-shadow: 0 0 0 4px rgba(59,130,246,.15);
168
+ }
169
+
170
+ .form-check-label {
171
+ color: #94a3b8;
172
+ }
173
+
174
+ /* ==========================================
175
+ BUTTONS
176
+ ========================================== */
177
+
178
+ .btn-danger {
179
+ width: 100%;
180
+
181
+ height: 50px;
182
+
183
+ border: none;
184
+
185
+ border-radius: 14px;
186
+
187
+ background: #051863;
188
+
189
+ color: white;
190
+
191
+ font-weight: 600;
192
+ }
193
+
194
+ .btn-danger:hover {
195
+ background: #dc2626;
196
+ }
197
+
198
+ .btn-primary {
199
+ width: 100%;
200
+
201
+ height: 54px;
202
+
203
+ border: none;
204
+
205
+ border-radius: 14px;
206
+
207
+ background: #2563eb;
208
+
209
+ color: white;
210
+
211
+ font-weight: 600;
212
+
213
+ margin-top: 10px;
214
+ }
215
+
216
+ .btn-primary:hover {
217
+ background: #1d4ed8;
218
+ }
219
+
220
+ /* ==========================================
221
+ LINKS
222
+ ========================================== */
223
+
224
+ .btn-link,
225
+ .text-end a {
226
+
227
+ color: #60a5fa;
228
+ text-decoration: none;
229
+ }
230
+
231
+ .btn-link:hover,
232
+ .text-end a:hover {
233
+ color: white;
234
+ }
235
+
236
+ .text-end {
237
+ display:flex;
238
+ justify-content:space-around;
239
+ margin-top: 10px;
240
+ }
241
+
242
+ /* ==========================================
243
+ ALERT
244
+ ========================================== */
245
+
246
+ .alert-danger {
247
+ background: rgba(239,68,68,.15);
248
+
249
+ border: 1px solid rgba(239,68,68,.3);
250
+
251
+ color: #fecaca;
252
+
253
+ border-radius: 12px;
254
+ }
255
+
256
+ /* ==========================================
257
+ MOBILE
258
+ ========================================== */
259
+
260
+ @media (max-width: 991px) {
261
+
262
+ .welcome-panel {
263
+ text-align: center;
264
+
265
+ padding-right: 0;
266
+
267
+ margin-bottom: 50px;
268
+ }
269
+
270
+ .logo-circle {
271
+ margin: 0 auto 25px;
272
+ }
273
+
274
+ .left-title {
275
+ font-size: 2.5rem;
276
+ }
277
+
278
+ .login-card {
279
+ max-width: 100%;
280
+ }
281
+ }
src/main/resources/static/css/components.css ADDED
@@ -0,0 +1,536 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===========================
2
+ COMPONENTS CSS
3
+ Buttons, Cards, Forms, Tables, Badges
4
+ =========================== */
5
+
6
+ /* ===========================
7
+ BUTTONS
8
+ =========================== */
9
+
10
+ .btn {
11
+ display: inline-flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ gap: 0.5rem;
15
+ padding: 0.5rem 1rem;
16
+ font-weight: 500;
17
+ border-radius: var(--radius);
18
+ border: none;
19
+ cursor: pointer;
20
+ transition: all var(--transition-duration);
21
+ text-decoration: none;
22
+ }
23
+
24
+ .btn-primary {
25
+ background-color: var(--primary);
26
+ color: var(--primary-foreground);
27
+ }
28
+
29
+ .btn-primary:hover {
30
+ opacity: 0.9;
31
+ }
32
+
33
+ .btn-secondary {
34
+ background-color: var(--secondary);
35
+ color: var(--secondary-foreground);
36
+ }
37
+
38
+ .btn-secondary:hover {
39
+ opacity: 0.8;
40
+ }
41
+
42
+ .btn-success {
43
+ background-color: var(--success);
44
+ color: white;
45
+ }
46
+
47
+ .btn-success:hover {
48
+ opacity: 0.9;
49
+ }
50
+
51
+ .btn-warning {
52
+ background-color: var(--warning);
53
+ color: white;
54
+ }
55
+
56
+ .btn-warning:hover {
57
+ opacity: 0.9;
58
+ }
59
+
60
+ .btn-destructive {
61
+ background-color: var(--destructive);
62
+ color: white;
63
+ }
64
+
65
+ .btn-destructive:hover {
66
+ opacity: 0.9;
67
+ }
68
+
69
+ .btn-outline {
70
+ background-color: transparent;
71
+ border: 1px solid var(--border);
72
+ color: var(--foreground);
73
+ }
74
+
75
+ .btn-outline:hover {
76
+ background-color: var(--secondary);
77
+ }
78
+
79
+ .btn-icon {
80
+ padding: 0.5rem;
81
+ width: 2.5rem;
82
+ height: 2.5rem;
83
+ }
84
+
85
+ .btn-sm {
86
+ padding: 0.25rem 0.75rem;
87
+ font-size: 0.875rem;
88
+ }
89
+
90
+ .btn-lg {
91
+ padding: 0.75rem 1.5rem;
92
+ font-size: 1.125rem;
93
+ }
94
+
95
+ /* ===========================
96
+ CARDS
97
+ =========================== */
98
+
99
+ .card {
100
+ background-color: var(--card);
101
+ border: 1px solid var(--border);
102
+ border-radius: var(--radius);
103
+ padding: 1.5rem;
104
+ transition: all var(--transition-duration);
105
+ }
106
+
107
+ .card:hover {
108
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
109
+ }
110
+
111
+ .card-header {
112
+ margin-bottom: 1rem;
113
+ padding-bottom: 1rem;
114
+ border-bottom: 1px solid var(--border);
115
+ }
116
+
117
+ .card-title {
118
+ font-size: 1.25rem;
119
+ font-weight: 700;
120
+ margin: 0;
121
+ }
122
+
123
+ .card-description {
124
+ color: var(--muted-foreground);
125
+ font-size: 0.875rem;
126
+ margin-top: 0.25rem;
127
+ }
128
+
129
+ .card-body {
130
+ padding: 0;
131
+ }
132
+
133
+ .card-footer {
134
+ margin-top: 1rem;
135
+ padding-top: 1rem;
136
+ border-top: 1px solid var(--border);
137
+ }
138
+
139
+ .stat-card {
140
+ background-color: var(--card);
141
+ border: 1px solid var(--border);
142
+ border-radius: calc(var(--radius) * 2);
143
+ padding: 1rem;
144
+ transition: all var(--transition-duration);
145
+ }
146
+
147
+ .stat-card-success {
148
+ border-color: rgba(16, 185, 129, 0.3);
149
+ }
150
+
151
+ .stat-card-warning {
152
+ border-color: rgba(245, 158, 11, 0.3);
153
+ }
154
+
155
+ .stat-card-destructive {
156
+ border-color: rgba(239, 68, 68, 0.3);
157
+ }
158
+
159
+ /* ===========================
160
+ FORMS
161
+ =========================== */
162
+
163
+ .form-group {
164
+ margin-bottom: 1rem;
165
+ }
166
+
167
+ .form-label {
168
+ display: block;
169
+ font-weight: 500;
170
+ margin-bottom: 0.5rem;
171
+ font-size: 0.875rem;
172
+ }
173
+
174
+ .form-control {
175
+ width: 100%;
176
+ padding: 0.5rem 1rem;
177
+ background-color: var(--input-background);
178
+ border: 1px solid var(--border);
179
+ border-radius: var(--radius);
180
+ color: var(--foreground);
181
+ font-size: 1rem;
182
+ transition: all var(--transition-duration);
183
+ }
184
+
185
+ .form-control:focus {
186
+ outline: none;
187
+ border-color: var(--primary);
188
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
189
+ }
190
+
191
+ .form-control:disabled {
192
+ opacity: 0.6;
193
+ cursor: not-allowed;
194
+ }
195
+
196
+ textarea.form-control {
197
+ min-height: 100px;
198
+ resize: vertical;
199
+ }
200
+
201
+ select.form-control {
202
+ cursor: pointer;
203
+ }
204
+
205
+ .form-text {
206
+ display: block;
207
+ margin-top: 0.25rem;
208
+ font-size: 0.875rem;
209
+ color: var(--muted-foreground);
210
+ }
211
+
212
+ .form-error {
213
+ display: block;
214
+ margin-top: 0.25rem;
215
+ font-size: 0.875rem;
216
+ color: var(--destructive);
217
+ }
218
+
219
+ /* Checkbox & Radio */
220
+ .form-check {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 0.5rem;
224
+ }
225
+
226
+ .form-check-input {
227
+ width: 1.25rem;
228
+ height: 1.25rem;
229
+ cursor: pointer;
230
+ }
231
+
232
+ /* ===========================
233
+ TABLES
234
+ =========================== */
235
+
236
+ .table-container {
237
+ background-color: var(--card);
238
+ border: 1px solid var(--border);
239
+ border-radius: calc(var(--radius) * 2);
240
+ overflow: hidden;
241
+ }
242
+
243
+ .table {
244
+ width: 100%;
245
+ border-collapse: collapse;
246
+ }
247
+
248
+ .table thead {
249
+ background-color: rgba(0, 0, 0, 0.03);
250
+ }
251
+
252
+ [data-theme="dark"] .table thead {
253
+ background-color: rgba(255, 255, 255, 0.05);
254
+ }
255
+
256
+ .table th {
257
+ padding: 1rem 1.5rem;
258
+ text-align: left;
259
+ font-weight: 600;
260
+ font-size: 0.875rem;
261
+ border-bottom: 1px solid var(--border);
262
+ }
263
+
264
+ .table th.text-center,
265
+ .table td.text-center {
266
+ text-align: center;
267
+ }
268
+
269
+ .table td {
270
+ padding: 1rem 1.5rem;
271
+ border-bottom: 1px solid var(--border);
272
+ }
273
+
274
+ .table tbody tr:last-child td {
275
+ border-bottom: none;
276
+ }
277
+
278
+ .table tbody tr:hover {
279
+ background-color: rgba(0, 0, 0, 0.02);
280
+ }
281
+
282
+ [data-theme="dark"] .table tbody tr:hover {
283
+ background-color: rgba(255, 255, 255, 0.05);
284
+ }
285
+
286
+ /* ===========================
287
+ BADGES
288
+ =========================== */
289
+
290
+ .badge {
291
+ display: inline-flex;
292
+ align-items: center;
293
+ padding: 0.25rem 0.75rem;
294
+ border-radius: 9999px;
295
+ font-size: 0.75rem;
296
+ font-weight: 600;
297
+ }
298
+
299
+ .badge-success {
300
+ background-color: rgba(16, 185, 129, 0.2);
301
+ color: var(--success);
302
+ }
303
+
304
+ .badge-warning {
305
+ background-color: rgba(245, 158, 11, 0.2);
306
+ color: var(--warning);
307
+ }
308
+
309
+ .badge-destructive {
310
+ background-color: rgba(239, 68, 68, 0.2);
311
+ color: var(--destructive);
312
+ }
313
+
314
+ .badge-primary {
315
+ background-color: rgba(59, 130, 246, 0.2);
316
+ color: var(--primary);
317
+ }
318
+
319
+ .badge-secondary {
320
+ background-color: var(--muted);
321
+ color: var(--muted-foreground);
322
+ }
323
+
324
+ /* ===========================
325
+ ALERTS
326
+ =========================== */
327
+
328
+ .alert {
329
+ padding: 1rem;
330
+ border-radius: var(--radius);
331
+ margin-bottom: 1rem;
332
+ }
333
+
334
+ .alert-success {
335
+ background-color: rgba(16, 185, 129, 0.1);
336
+ border: 1px solid rgba(16, 185, 129, 0.3);
337
+ color: var(--success);
338
+ }
339
+
340
+ .alert-warning {
341
+ background-color: rgba(245, 158, 11, 0.1);
342
+ border: 1px solid rgba(245, 158, 11, 0.3);
343
+ color: var(--warning);
344
+ }
345
+
346
+ .alert-destructive {
347
+ background-color: rgba(239, 68, 68, 0.1);
348
+ border: 1px solid rgba(239, 68, 68, 0.3);
349
+ color: var(--destructive);
350
+ }
351
+
352
+ .alert-info {
353
+ background-color: rgba(59, 130, 246, 0.1);
354
+ border: 1px solid rgba(59, 130, 246, 0.3);
355
+ color: var(--primary);
356
+ }
357
+
358
+ /* ===========================
359
+ MODALS
360
+ =========================== */
361
+
362
+ .modal-overlay {
363
+ position: fixed;
364
+ top: 0;
365
+ left: 0;
366
+ width: 100%;
367
+ height: 100%;
368
+ background-color: rgba(0, 0, 0, 0.5);
369
+ display: flex;
370
+ align-items: center;
371
+ justify-content: center;
372
+ z-index: 1000;
373
+ padding: 1rem;
374
+ }
375
+
376
+ .modal {
377
+ background-color: var(--card);
378
+ border: 1px solid var(--border);
379
+ border-radius: calc(var(--radius) * 2);
380
+ padding: 1.5rem;
381
+ max-width: 600px;
382
+ width: 100%;
383
+ max-height: 90vh;
384
+ overflow-y: auto;
385
+ }
386
+
387
+ .modal-header {
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: space-between;
391
+ margin-bottom: 1rem;
392
+ padding-bottom: 1rem;
393
+ border-bottom: 1px solid var(--border);
394
+ }
395
+
396
+ .modal-title {
397
+ font-size: 1.25rem;
398
+ font-weight: 700;
399
+ margin: 0;
400
+ }
401
+
402
+ .modal-close {
403
+ background: none;
404
+ border: none;
405
+ cursor: pointer;
406
+ padding: 0.25rem;
407
+ color: var(--muted-foreground);
408
+ }
409
+
410
+ .modal-close:hover {
411
+ color: var(--foreground);
412
+ }
413
+
414
+ .modal-body {
415
+ margin-bottom: 1rem;
416
+ }
417
+
418
+ .modal-footer {
419
+ display: flex;
420
+ gap: 0.75rem;
421
+ justify-content: flex-end;
422
+ padding-top: 1rem;
423
+ border-top: 1px solid var(--border);
424
+ }
425
+
426
+ /* ===========================
427
+ HEADER & NAVIGATION
428
+ =========================== */
429
+
430
+ .header {
431
+ background-color: var(--card);
432
+ border-bottom: 1px solid var(--border);
433
+ padding: 1rem;
434
+ }
435
+
436
+ .nav {
437
+ display: flex;
438
+ align-items: center;
439
+ justify-content: space-between;
440
+ max-width: 1280px;
441
+ margin: 0 auto;
442
+ }
443
+
444
+ .nav-brand {
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 1rem;
448
+ font-size: 1.5rem;
449
+ font-weight: 700;
450
+ text-decoration: none;
451
+ color: var(--foreground);
452
+ }
453
+
454
+ .nav-menu {
455
+ display: flex;
456
+ align-items: center;
457
+ gap: 1rem;
458
+ list-style: none;
459
+ }
460
+
461
+ .nav-link {
462
+ text-decoration: none;
463
+ color: var(--foreground);
464
+ padding: 0.5rem 1rem;
465
+ border-radius: var(--radius);
466
+ transition: all var(--transition-duration);
467
+ }
468
+
469
+ .nav-link:hover {
470
+ background-color: var(--secondary);
471
+ }
472
+
473
+ /* ===========================
474
+ PROGRESS BARS
475
+ =========================== */
476
+
477
+ .progress {
478
+ width: 100%;
479
+ height: 0.5rem;
480
+ background-color: var(--secondary);
481
+ border-radius: 9999px;
482
+ overflow: hidden;
483
+ }
484
+
485
+ .progress-bar {
486
+ height: 100%;
487
+ background: linear-gradient(to right, var(--success), var(--primary));
488
+ border-radius: 9999px;
489
+ transition: width var(--transition-duration);
490
+ }
491
+
492
+ /* ===========================
493
+ LOADING SPINNER
494
+ =========================== */
495
+
496
+ .spinner {
497
+ display: inline-block;
498
+ width: 1.5rem;
499
+ height: 1.5rem;
500
+ border: 2px solid var(--border);
501
+ border-top-color: var(--primary);
502
+ border-radius: 50%;
503
+ animation: spin 0.8s linear infinite;
504
+ }
505
+
506
+ @keyframes spin {
507
+ to { transform: rotate(360deg); }
508
+ }
509
+
510
+ /* ===========================
511
+ ANIMATIONS
512
+ =========================== */
513
+
514
+ @keyframes fadeIn {
515
+ from { opacity: 0; }
516
+ to { opacity: 1; }
517
+ }
518
+
519
+ @keyframes slideUp {
520
+ from {
521
+ opacity: 0;
522
+ transform: translateY(20px);
523
+ }
524
+ to {
525
+ opacity: 1;
526
+ transform: translateY(0);
527
+ }
528
+ }
529
+
530
+ .animate-fade-in {
531
+ animation: fadeIn 0.3s ease-in-out;
532
+ }
533
+
534
+ .animate-slide-up {
535
+ animation: slideUp 0.3s ease-in-out;
536
+ }
src/main/resources/static/css/layout.css ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* =====================================
2
+ HEADER
3
+ ===================================== */
4
+
5
+ .app-header {
6
+ background: #0f172a;
7
+
8
+ border-bottom: 1px solid rgba(59,130,246,.15);
9
+
10
+ position: sticky;
11
+ top: 0;
12
+
13
+ z-index: 999;
14
+ }
15
+
16
+ /* =====================================
17
+ LAYOUT
18
+ ===================================== */
19
+
20
+ .header-content {
21
+ height: 75px;
22
+
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ }
27
+
28
+ .header-left {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 18px;
32
+ }
33
+
34
+ .header-right {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 20px;
38
+ }
39
+
40
+ /* =====================================
41
+ LOGO
42
+ ===================================== */
43
+
44
+ .logo-box {
45
+ width: 60px;
46
+ height: 60px;
47
+
48
+ border-radius: 18px;
49
+
50
+ background: rgba(37,99,235,.15);
51
+
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+
56
+ text-decoration: none;
57
+ }
58
+
59
+ .logo-box i {
60
+ font-size: 28px;
61
+ color: #3b82f6;
62
+ }
63
+
64
+ /* =====================================
65
+ TITLE
66
+ ===================================== */
67
+
68
+ .header-title {
69
+ color: white;
70
+
71
+ font-size: 1.5rem;
72
+ font-weight: 700;
73
+
74
+ margin: 0;
75
+ }
76
+
77
+ .header-subtitle {
78
+ color: #94a3b8;
79
+
80
+ font-size: .9rem;
81
+ }
82
+
83
+ /* =====================================
84
+ THEME BUTTON
85
+ ===================================== */
86
+
87
+ .theme-btn {
88
+ width: 46px;
89
+ height: 46px;
90
+
91
+ border: none;
92
+
93
+ border-radius: 14px;
94
+
95
+ background: #1e293b;
96
+
97
+ color: #cbd5e1;
98
+
99
+ transition: .3s;
100
+ }
101
+
102
+ .theme-btn:hover {
103
+ background: #2563eb;
104
+ color: white;
105
+ }
106
+
107
+ /* =====================================
108
+ USER BOX
109
+ ===================================== */
110
+
111
+ .user-box {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 12px;
115
+
116
+ background: #1e293b;
117
+
118
+ border: 1px solid rgba(59,130,246,.15);
119
+
120
+ padding: 8px 14px;
121
+
122
+ border-radius: 16px;
123
+ }
124
+
125
+ .user-avatar {
126
+ width: 42px;
127
+ height: 42px;
128
+
129
+ border-radius: 12px;
130
+
131
+ background: rgba(59,130,246,.15);
132
+
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ }
137
+
138
+ .user-avatar i {
139
+ color: #60a5fa;
140
+ font-size: 18px;
141
+ }
142
+
143
+ .user-name {
144
+ color: white;
145
+
146
+ font-weight: 600;
147
+
148
+ display: block;
149
+ }
150
+
151
+ .user-info small {
152
+ color: #94a3b8;
153
+ }
154
+
155
+ /* =====================================
156
+ FOOTER
157
+ ===================================== */
158
+
159
+ .app-footer {
160
+ background: #0f172a;
161
+
162
+ border-top: 1px solid rgba(59,130,246,.15);
163
+
164
+ margin-top: 0px;
165
+
166
+ padding-top: 10px;
167
+ }
168
+
169
+ .footer-content {
170
+ display: flex;
171
+
172
+ justify-content: space-between;
173
+
174
+ align-items: center;
175
+
176
+ gap: 40px;
177
+ margin-top: 30px;
178
+ padding-bottom: 30px;
179
+ }
180
+
181
+ /* =====================================
182
+ LEFT
183
+ ===================================== */
184
+
185
+ .footer-left h5 {
186
+ color: white;
187
+
188
+ margin-bottom: 10px;
189
+
190
+ font-weight: 700;
191
+ }
192
+
193
+ .footer-left h5 i {
194
+ color: #3b82f6;
195
+
196
+ margin-right: 8px;
197
+ }
198
+
199
+ .footer-left p {
200
+ color: #94a3b8;
201
+
202
+ margin: 0;
203
+ }
204
+
205
+ /* =====================================
206
+ CENTER
207
+ ===================================== */
208
+
209
+ .footer-center {
210
+ display: flex;
211
+
212
+ gap: 25px;
213
+ }
214
+
215
+ .footer-center a {
216
+ color: #94a3b8;
217
+
218
+ text-decoration: none;
219
+
220
+ transition: .3s;
221
+ }
222
+
223
+ .footer-center a:hover {
224
+ color: #60a5fa;
225
+ }
226
+
227
+ /* =====================================
228
+ RIGHT
229
+ ===================================== */
230
+
231
+ .footer-right {
232
+ display: flex;
233
+
234
+ gap: 15px;
235
+ }
236
+
237
+ .footer-right a {
238
+ width: 42px;
239
+ height: 42px;
240
+
241
+ border-radius: 12px;
242
+
243
+ background: #1e293b;
244
+
245
+ color: #94a3b8;
246
+
247
+ display: flex;
248
+
249
+ align-items: center;
250
+ justify-content: center;
251
+
252
+ text-decoration: none;
253
+
254
+ transition: .3s;
255
+ }
256
+
257
+ .footer-right a:hover {
258
+ background: #2563eb;
259
+ color: white;
260
+
261
+ transform: translateY(-3px);
262
+ }
263
+
264
+ /* =====================================
265
+ COPYRIGHT
266
+ ===================================== */
267
+
268
+ .footer-bottom {
269
+ text-align: center;
270
+
271
+ color: #64748b;
272
+
273
+ border-top: 1px solid rgba(255,255,255,.05);
274
+
275
+ padding: 20px 0;
276
+
277
+ font-size: .9rem;
278
+ }
279
+
280
+ /* =====================================
281
+ MOBILE
282
+ ===================================== */
283
+
284
+ @media (max-width: 768px) {
285
+
286
+ .footer-content {
287
+ flex-direction: column;
288
+
289
+ text-align: center;
290
+ }
291
+
292
+ .footer-center {
293
+ flex-wrap: wrap;
294
+
295
+ justify-content: center;
296
+ }
297
+ }
298
+ /* =====================================
299
+ MOBILE
300
+ ===================================== */
301
+
302
+ @media (max-width: 768px) {
303
+
304
+ .header-subtitle,
305
+ .user-info {
306
+ display: none;
307
+ }
308
+
309
+ .header-title {
310
+ font-size: 1.1rem;
311
+ }
312
+
313
+ .header-content {
314
+ height: 75px;
315
+ }
316
+ }
src/main/resources/static/css/style.css ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Smart Attendance System - Main Styles */
2
+
3
+ :root {
4
+ --primary-gradient: linear-gradient(to right, #6366f1, #8b5cf6);
5
+ --secondary-gradient: linear-gradient(to right, #8b5cf6, #ec4899);
6
+ --admin-gradient: linear-gradient(to right, #ec4899, #f97316);
7
+ }
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
17
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
18
+ sans-serif;
19
+ -webkit-font-smoothing: antialiased;
20
+ -moz-osx-font-smoothing: grayscale;
21
+ }
22
+
23
+ /* Glassmorphism Effect */
24
+ .glass {
25
+ background: rgba(255, 255, 255, 0.05);
26
+ backdrop-filter: blur(10px);
27
+ -webkit-backdrop-filter: blur(10px);
28
+ border: 1px solid rgba(255, 255, 255, 0.1);
29
+ }
30
+
31
+ /* Gradient Text */
32
+ .gradient-text {
33
+ background: linear-gradient(to right, #6366f1, #8b5cf6, #ec4899);
34
+ -webkit-background-clip: text;
35
+ -webkit-text-fill-color: transparent;
36
+ background-clip: text;
37
+ }
38
+
39
+ /* Smooth Transitions */
40
+ .transition-smooth {
41
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
42
+ }
43
+
44
+ /* Card Hover Effect */
45
+ .card-hover {
46
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
47
+ }
48
+
49
+ .card-hover:hover {
50
+ transform: translateY(-4px);
51
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
52
+ }
53
+
54
+ /* Custom Scrollbar */
55
+ ::-webkit-scrollbar {
56
+ width: 8px;
57
+ height: 8px;
58
+ }
59
+
60
+ ::-webkit-scrollbar-track {
61
+ background: #1e293b;
62
+ }
63
+
64
+ ::-webkit-scrollbar-thumb {
65
+ background: #475569;
66
+ border-radius: 4px;
67
+ }
68
+
69
+ ::-webkit-scrollbar-thumb:hover {
70
+ background: #64748b;
71
+ }
72
+
73
+ /* Loading Animation */
74
+ @keyframes pulse {
75
+ 0%, 100% {
76
+ opacity: 1;
77
+ }
78
+ 50% {
79
+ opacity: 0.5;
80
+ }
81
+ }
82
+
83
+ .animate-pulse {
84
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
85
+ }
86
+
87
+ /* Fade In Animation */
88
+ @keyframes fadeIn {
89
+ from {
90
+ opacity: 0;
91
+ transform: translateY(20px);
92
+ }
93
+ to {
94
+ opacity: 1;
95
+ transform: translateY(0);
96
+ }
97
+ }
98
+
99
+ .fade-in {
100
+ animation: fadeIn 0.6s ease-out;
101
+ }
102
+
103
+ /* Button Styles */
104
+ .btn-primary {
105
+ background: linear-gradient(to right, #6366f1, #8b5cf6);
106
+ color: white;
107
+ padding: 0.75rem 1.5rem;
108
+ border-radius: 0.75rem;
109
+ font-weight: 500;
110
+ transition: all 0.3s ease;
111
+ border: none;
112
+ cursor: pointer;
113
+ }
114
+
115
+ .btn-primary:hover {
116
+ background: linear-gradient(to right, #4f46e5, #7c3aed);
117
+ transform: translateY(-2px);
118
+ box-shadow: 0 10px 20px rgba(99, 102, 241, 0.3);
119
+ }
120
+
121
+ /* Responsive */
122
+ @media (max-width: 768px) {
123
+ .sidebar {
124
+ position: fixed;
125
+ left: -100%;
126
+ transition: left 0.3s ease;
127
+ z-index: 50;
128
+ }
129
+
130
+ .sidebar.open {
131
+ left: 0;
132
+ }
133
+ }
src/main/resources/static/css/theme.css ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===========================
2
+ FACE ID ATTENDANCE SYSTEM
3
+ Theme CSS - Light & Dark Mode
4
+ =========================== */
5
+
6
+ :root {
7
+ /* Light Mode Colors */
8
+ --background: #ffffff;
9
+ --foreground: #0F1117;
10
+ --card: #ffffff;
11
+ --card-foreground: #0F1117;
12
+ --popover: #ffffff;
13
+ --popover-foreground: #0F1117;
14
+ --primary: #3B82F6;
15
+ --primary-foreground: #ffffff;
16
+ --secondary: #F3F4F6;
17
+ --secondary-foreground: #0F1117;
18
+ --muted: #F3F4F6;
19
+ --muted-foreground: #6B7280;
20
+ --accent: #F3F4F6;
21
+ --accent-foreground: #0F1117;
22
+ --destructive: #EF4444;
23
+ --destructive-foreground: #ffffff;
24
+ --border: #E5E7EB;
25
+ --input: #E5E7EB;
26
+ --input-background: #ffffff;
27
+ --ring: #3B82F6;
28
+ --success: #10B981;
29
+ --warning: #F59E0B;
30
+ --info: #3B82F6;
31
+
32
+ /* Spacing */
33
+ --radius: 0.5rem;
34
+ --transition-duration: 300ms;
35
+ }
36
+
37
+ [data-theme="dark"] {
38
+ /* Dark Mode Colors */
39
+ --background: #0F1117;
40
+ --foreground: #F9FAFB;
41
+ --card: #1B1F2A;
42
+ --card-foreground: #F9FAFB;
43
+ --popover: #1B1F2A;
44
+ --popover-foreground: #F9FAFB;
45
+ --primary: #60A5FA;
46
+ --primary-foreground: #0F1117;
47
+ --secondary: #1F2937;
48
+ --secondary-foreground: #F9FAFB;
49
+ --muted: #1F2937;
50
+ --muted-foreground: #9CA3AF;
51
+ --accent: #1F2937;
52
+ --accent-foreground: #F9FAFB;
53
+ --destructive: #EF4444;
54
+ --destructive-foreground: #ffffff;
55
+ --border: #374151;
56
+ --input: #374151;
57
+ --input-background: #1F2937;
58
+ --ring: #60A5FA;
59
+ --success: #10B981;
60
+ --warning: #F59E0B;
61
+ --info: #60A5FA;
62
+ }
63
+
64
+ /* ===========================
65
+ RESET & BASE STYLES
66
+ =========================== */
67
+
68
+ * {
69
+ margin: 0;
70
+ padding: 0;
71
+ box-sizing: border-box;
72
+ }
73
+
74
+ body {
75
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
76
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
77
+ background-color: var(--background);
78
+ color: var(--foreground);
79
+ line-height: 1.5;
80
+ transition: background-color var(--transition-duration), color var(--transition-duration);
81
+ }
82
+
83
+ h1, h2, h3, h4, h5, h6 {
84
+ font-weight: 700;
85
+ line-height: 1.2;
86
+ }
87
+
88
+ h1 { font-size: 2rem; }
89
+ h2 { font-size: 1.5rem; }
90
+ h3 { font-size: 1.25rem; }
91
+
92
+ /* ===========================
93
+ SCROLLBAR STYLING
94
+ =========================== */
95
+
96
+ ::-webkit-scrollbar {
97
+ width: 8px;
98
+ height: 8px;
99
+ }
100
+
101
+ ::-webkit-scrollbar-track {
102
+ background: var(--secondary);
103
+ }
104
+
105
+ ::-webkit-scrollbar-thumb {
106
+ background: var(--muted-foreground);
107
+ border-radius: 4px;
108
+ }
109
+
110
+ ::-webkit-scrollbar-thumb:hover {
111
+ background: var(--foreground);
112
+ }
113
+
114
+ /* ===========================
115
+ UTILITY CLASSES
116
+ =========================== */
117
+
118
+ .container {
119
+ max-width: 1280px;
120
+ margin: 0 auto;
121
+ padding: 0 1.5rem;
122
+ }
123
+
124
+ .text-center { text-align: center; }
125
+ .text-left { text-align: left; }
126
+ .text-right { text-align: right; }
127
+
128
+ .font-bold { font-weight: 700; }
129
+ .font-semibold { font-weight: 600; }
130
+ .font-medium { font-weight: 500; }
131
+
132
+ .text-sm { font-size: 0.875rem; }
133
+ .text-xs { font-size: 0.75rem; }
134
+ .text-lg { font-size: 1.125rem; }
135
+ .text-xl { font-size: 1.25rem; }
136
+ .text-2xl { font-size: 1.5rem; }
137
+ .text-3xl { font-size: 1.875rem; }
138
+
139
+ .text-muted { color: var(--muted-foreground); }
140
+ .text-primary { color: var(--primary); }
141
+ .text-success { color: var(--success); }
142
+ .text-warning { color: var(--warning); }
143
+ .text-destructive { color: var(--destructive); }
144
+
145
+ .bg-card { background-color: var(--card); }
146
+ .bg-secondary { background-color: var(--secondary); }
147
+ .bg-primary { background-color: var(--primary); }
148
+ .bg-success { background-color: var(--success); }
149
+ .bg-warning { background-color: var(--warning); }
150
+ .bg-destructive { background-color: var(--destructive); }
151
+
152
+ .border { border: 1px solid var(--border); }
153
+ .border-primary { border-color: var(--primary); }
154
+ .border-success { border-color: var(--success); }
155
+ .border-warning { border-color: var(--warning); }
156
+ .border-destructive { border-color: var(--destructive); }
157
+
158
+ .rounded { border-radius: var(--radius); }
159
+ .rounded-lg { border-radius: calc(var(--radius) * 1.5); }
160
+ .rounded-xl { border-radius: calc(var(--radius) * 2); }
161
+ .rounded-full { border-radius: 9999px; }
162
+
163
+ .shadow { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); }
164
+ .shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
165
+
166
+ .transition-all {
167
+ transition: all var(--transition-duration);
168
+ }
169
+
170
+ /* Flexbox */
171
+ .flex { display: flex; }
172
+ .flex-col { flex-direction: column; }
173
+ .items-center { align-items: center; }
174
+ .items-start { align-items: flex-start; }
175
+ .items-end { align-items: flex-end; }
176
+ .justify-center { justify-content: center; }
177
+ .justify-between { justify-content: space-between; }
178
+ .justify-end { justify-content: flex-end; }
179
+ .gap-1 { gap: 0.25rem; }
180
+ .gap-2 { gap: 0.5rem; }
181
+ .gap-3 { gap: 0.75rem; }
182
+ .gap-4 { gap: 1rem; }
183
+ .gap-6 { gap: 1.5rem; }
184
+
185
+ /* Grid */
186
+ .grid { display: grid; }
187
+ .grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
188
+ .grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
189
+ .grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
190
+ .grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
191
+ .grid-cols-5 { grid-template-columns: repeat(5, 1fr); }
192
+
193
+ /* Spacing */
194
+ .p-1 { padding: 0.25rem; }
195
+ .p-2 { padding: 0.5rem; }
196
+ .p-3 { padding: 0.75rem; }
197
+ .p-4 { padding: 1rem; }
198
+ .p-6 { padding: 1.5rem; }
199
+ .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
200
+ .px-4 { padding-left: 1rem; padding-right: 1rem; }
201
+ .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
202
+ .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
203
+ .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
204
+ .py-4 { padding-top: 1rem; padding-bottom: 1rem; }
205
+
206
+ .m-0 { margin: 0; }
207
+ .mb-1 { margin-bottom: 0.25rem; }
208
+ .mb-2 { margin-bottom: 0.5rem; }
209
+ .mb-3 { margin-bottom: 0.75rem; }
210
+ .mb-4 { margin-bottom: 1rem; }
211
+ .mb-6 { margin-bottom: 1.5rem; }
212
+ .mt-1 { margin-top: 0.25rem; }
213
+ .mt-2 { margin-top: 0.5rem; }
214
+ .mt-4 { margin-top: 1rem; }
215
+ .mx-auto { margin-left: auto; margin-right: auto; }
216
+
217
+ /* Width & Height */
218
+ .w-full { width: 100%; }
219
+ .h-full { height: 100%; }
220
+ .min-h-screen { min-height: 100vh; }
221
+
222
+ /* Display */
223
+ .hidden { display: none; }
224
+ .block { display: block; }
225
+ .inline-block { display: inline-block; }
226
+ .inline-flex { display: inline-flex; }
227
+
228
+ /* Position */
229
+ .relative { position: relative; }
230
+ .absolute { position: absolute; }
231
+ .fixed { position: fixed; }
232
+
233
+ /* Overflow */
234
+ .overflow-hidden { overflow: hidden; }
235
+ .overflow-auto { overflow: auto; }
src/main/resources/static/js/main.js ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * FACE ID ATTENDANCE SYSTEM
3
+ * Main JavaScript - Theme Toggle & Utilities
4
+ */
5
+
6
+ // ===========================
7
+ // THEME MANAGEMENT
8
+ // ===========================
9
+
10
+ function initTheme() {
11
+ const savedTheme = localStorage.getItem('theme') || 'light';
12
+ document.documentElement.setAttribute('data-theme', savedTheme);
13
+ updateThemeIcon(savedTheme);
14
+ }
15
+
16
+ function toggleTheme() {
17
+ const currentTheme = document.documentElement.getAttribute('data-theme');
18
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
19
+ document.documentElement.setAttribute('data-theme', newTheme);
20
+ localStorage.setItem('theme', newTheme);
21
+ updateThemeIcon(newTheme);
22
+ }
23
+
24
+ function updateThemeIcon(theme) {
25
+ const themeBtn = document.getElementById('theme-toggle');
26
+ if (themeBtn) {
27
+ const icon = themeBtn.querySelector('svg');
28
+ if (icon) {
29
+ icon.innerHTML = theme === 'light'
30
+ ? '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>'
31
+ : '<circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line>';
32
+ }
33
+ }
34
+ }
35
+
36
+ // Clock, modals, validation
37
+ function updateClock() {
38
+ const clockElements = document.querySelectorAll('.clock');
39
+ if (clockElements.length === 0) return;
40
+ const now = new Date();
41
+ clockElements.forEach(clock => {
42
+ const timeEl = clock.querySelector('.clock-time');
43
+ const dateEl = clock.querySelector('.clock-date');
44
+ if (timeEl) timeEl.textContent = now.toLocaleTimeString('vi-VN');
45
+ if (dateEl) dateEl.textContent = now.toLocaleDateString('vi-VN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
46
+ });
47
+ }
48
+
49
+ function openModal(modalId) {
50
+ const modal = document.getElementById(modalId);
51
+ if (modal) {
52
+ modal.style.display = 'flex';
53
+ document.body.style.overflow = 'hidden';
54
+ }
55
+ }
56
+
57
+ function closeModal(modalId) {
58
+ const modal = document.getElementById(modalId);
59
+ if (modal) {
60
+ modal.style.display = 'none';
61
+ document.body.style.overflow = 'auto';
62
+ }
63
+ }
64
+
65
+ function showAlert(message, type = 'info') {
66
+ alert(message);
67
+ }
68
+
69
+ function togglePassword(inputId) {
70
+ const input = document.getElementById(inputId);
71
+ if (!input) return;
72
+ input.type = input.type === 'password' ? 'text' : 'password';
73
+ }
74
+
75
+ // Initialize
76
+ document.addEventListener('DOMContentLoaded', function() {
77
+ initTheme();
78
+ updateClock();
79
+ setInterval(updateClock, 1000);
80
+ });
81
+
82
+ window.AttendanceSystem = {
83
+ toggleTheme,
84
+ openModal,
85
+ closeModal,
86
+ showAlert,
87
+ togglePassword
88
+ };
src/main/webapp/WEB-INF/views/attendances/homeAttendance.jsp ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page contentType="text/html;charset=UTF-8" %>
2
+
3
+
4
+ <html lang="en">
5
+
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <title>Home Attendance</title>
10
+ <link href="/css/attendance.css" rel="stylesheet">
11
+ </head>
12
+
13
+ <body>
14
+
15
+ <!-- =======================================================================
16
+ HEADER
17
+ ==========================================================================-->
18
+ <header class="header">
19
+ <div class="header-left">
20
+ <div class="header-logo">
21
+ <a href="/home">MyApp</a>
22
+ </div>
23
+
24
+ <div class="header-info">
25
+ <h1 class="header-title">Home Attendance 1</h1>
26
+ <div class="header-time">22/22/22</div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="header-right">
31
+
32
+ <div class="header-clock" id="clock">
33
+ 00:00:00
34
+ </div>
35
+
36
+ <button class="header-btn theme-toggle" id="themeToggle">
37
+ 🌙
38
+ </button>
39
+
40
+
41
+
42
+ </div>
43
+ </header>
44
+ <!-- =======================================================================
45
+ BODY
46
+ ==========================================================================-->
47
+ <div class="container">
48
+ <div class="container-attend-left history">
49
+ <h3 class="history-title">Lịch sử chấm công</h3>
50
+
51
+ <ul class="history-list">
52
+
53
+ <li class="history-item">
54
+ <div class="history-card">
55
+ <span class="history-date">
56
+ Ngày 01/01/2024
57
+ </span>
58
+
59
+ <span class="history-status status-in">
60
+ Vào lúc 08:00
61
+ </span>
62
+
63
+ <span class="history-status status-out">
64
+ Ra lúc 17:00
65
+ </span>
66
+ </div>
67
+ </li>
68
+
69
+ </ul>
70
+ </div>
71
+ <div class="container-attend-main">
72
+
73
+ <div class="face-scanner scanner-card">
74
+
75
+ <!-- <video id="camera" autoplay playsinline></video> -->
76
+
77
+ <div class="scan-overlay">
78
+ <div class="scan-frame"></div>
79
+ <div class="scan-line"></div>
80
+ </div>
81
+
82
+ <div class="result-card success-result">
83
+ ✓ Nhận diện thành công
84
+ </div>
85
+
86
+ </div>
87
+
88
+ <div class="face-guide guide-card">
89
+ <p class="guide-title">
90
+ Hướng dẫn chấm công bằng nhận diện khuôn mặt:
91
+ </p>
92
+
93
+ <ol class="guide-list">
94
+ <li>Đứng cách camera khoảng 50cm - 1m</li>
95
+ <li>Nhìn thẳng vào camera</li>
96
+ <li>Giữ khuôn mặt trong khung hình</li>
97
+ <li>Hệ thống sẽ tự động nhận diện và ghi nhận</li>
98
+ </ol>
99
+ </div>
100
+
101
+ </div>
102
+ <div class="container-attend-right">
103
+
104
+ <h3 class="attendance-title">
105
+ Thống kê hôm nay
106
+ </h3>
107
+
108
+ <div class="stat-card success-card">
109
+ <p class="attendance-present">
110
+ Đã chấm công
111
+ </p>
112
+ <span class="stat-number">42</span>
113
+ </div>
114
+
115
+ <div class="stat-card warning-card">
116
+ <p class="attendance-late">
117
+ Đi muộn
118
+ </p>
119
+ <span class="stat-number">8</span>
120
+ </div>
121
+
122
+ <div class="stat-card danger-card">
123
+ <p class="attendance-absent">
124
+ Chưa chấm công
125
+ </p>
126
+ <span class="stat-number">12</span>
127
+ </div>
128
+
129
+ <div class="attendance-info">
130
+ <p class="attendance-sum">
131
+ Tổng nhân viên: 50
132
+ </p>
133
+
134
+ <p class="attendance-date">
135
+ Giờ làm việc: 08:00 - 17:00
136
+ </p>
137
+ </div>
138
+
139
+ </div>
140
+
141
+ </body>
142
+
143
+ </html>
src/main/webapp/WEB-INF/views/auths/forgotPass.jsp ADDED
File without changes
src/main/webapp/WEB-INF/views/auths/login.jsp ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+ <%@ include file="/WEB-INF/views/layouts/header.jsp" %>
4
+
5
+ <div class="min-h-screen bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-950 flex items-center justify-center p-4">
6
+ <div class="w-full max-w-md bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-8">
7
+ <div class="text-center mb-8">
8
+ <div class="w-16 h-16 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
9
+ <i data-lucide="camera" class="w-8 h-8 text-white"></i>
10
+ </div>
11
+ <h1 class="text-3xl font-bold text-white mb-2">Đăng nhập</h1>
12
+ <p class="text-slate-400">Truy cập vào hệ thống quản lý điểm danh</p>
13
+ </div>
14
+
15
+ <form action="${pageContext.request.contextPath}/login" method="POST" class="space-y-4">
16
+ <div class="space-y-2">
17
+ <label for="email" class="block text-sm text-slate-300">Email</label>
18
+ <input
19
+ type="email"
20
+ id="email"
21
+ name="email"
22
+ placeholder="example@company.com"
23
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition"
24
+ required
25
+ />
26
+ </div>
27
+
28
+ <div class="space-y-2">
29
+ <label for="password" class="block text-sm text-slate-300">Mật khẩu</label>
30
+ <div class="relative">
31
+ <input
32
+ type="password"
33
+ id="password"
34
+ name="password"
35
+ placeholder="••••••••"
36
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition pr-10"
37
+ required
38
+ />
39
+ <button
40
+ type="button"
41
+ onclick="togglePassword()"
42
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white"
43
+ >
44
+ <i data-lucide="eye" id="eyeIcon" class="w-5 h-5"></i>
45
+ </button>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="space-y-3 pt-4">
50
+ <button
51
+ type="submit"
52
+ formaction="${pageContext.request.contextPath}/employee/dashboard"
53
+ class="w-full px-4 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-xl font-medium transition"
54
+ >
55
+ Đăng nhập với vai trò Nhân viên
56
+ </button>
57
+ <button
58
+ type="submit"
59
+ formaction="${pageContext.request.contextPath}/teacher/dashboard"
60
+ class="w-full px-4 py-3 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 rounded-xl font-medium transition"
61
+ >
62
+ Đăng nhập với vai trò Quản lý
63
+ </button>
64
+ <button
65
+ type="submit"
66
+ formaction="${pageContext.request.contextPath}/admin/dashboard"
67
+ class="w-full px-4 py-3 bg-gradient-to-r from-pink-600 to-orange-600 hover:from-pink-700 hover:to-orange-700 rounded-xl font-medium transition"
68
+ >
69
+ Đăng nhập với vai trò Admin
70
+ </button>
71
+ </div>
72
+ </form>
73
+
74
+ <div class="text-center text-sm text-slate-400 mt-6">
75
+ Chưa có tài khoản?
76
+ <a href="${pageContext.request.contextPath}/register" class="text-indigo-400 hover:text-indigo-300 font-medium">
77
+ Đăng ký ngay
78
+ </a>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <script>
84
+ function togglePassword() {
85
+ const passwordInput = document.getElementById('password');
86
+ const eyeIcon = document.getElementById('eyeIcon');
87
+ if (passwordInput.type === 'password') {
88
+ passwordInput.type = 'text';
89
+ eyeIcon.setAttribute('data-lucide', 'eye-off');
90
+ } else {
91
+ passwordInput.type = 'password';
92
+ eyeIcon.setAttribute('data-lucide', 'eye');
93
+ }
94
+ lucide.createIcons();
95
+ }
96
+ </script>
97
+
98
+ <%@ include file="/WEB-INF/views/layouts/footer.jsp" %>
src/main/webapp/WEB-INF/views/auths/register.jsp ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+ <%@ include file="/WEB-INF/views/layouts/header.jsp" %>
4
+
5
+ <div class="min-h-screen bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-950 flex items-center justify-center p-4">
6
+ <div class="w-full max-w-md bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-8">
7
+ <div class="text-center mb-8">
8
+ <div class="w-16 h-16 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
9
+ <i data-lucide="camera" class="w-8 h-8 text-white"></i>
10
+ </div>
11
+ <h1 class="text-3xl font-bold text-white mb-2">Đăng ký</h1>
12
+ <p class="text-slate-400">Tạo tài khoản mới để sử dụng hệ thống</p>
13
+ </div>
14
+
15
+ <form action="${pageContext.request.contextPath}/register" method="POST" class="space-y-4">
16
+ <div class="space-y-2">
17
+ <label for="fullName" class="block text-sm text-slate-300">Họ và tên</label>
18
+ <input
19
+ type="text"
20
+ id="fullName"
21
+ name="fullName"
22
+ placeholder="Nguyễn Văn A"
23
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition"
24
+ required
25
+ />
26
+ </div>
27
+
28
+ <div class="space-y-2">
29
+ <label for="email" class="block text-sm text-slate-300">Email</label>
30
+ <input
31
+ type="email"
32
+ id="email"
33
+ name="email"
34
+ placeholder="example@company.com"
35
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition"
36
+ required
37
+ />
38
+ </div>
39
+
40
+ <div class="space-y-2">
41
+ <label for="password" class="block text-sm text-slate-300">Mật khẩu</label>
42
+ <input
43
+ type="password"
44
+ id="password"
45
+ name="password"
46
+ placeholder="••••••••"
47
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition"
48
+ required
49
+ />
50
+ </div>
51
+
52
+ <div class="space-y-2">
53
+ <label for="confirmPassword" class="block text-sm text-slate-300">Xác nhận mật khẩu</label>
54
+ <input
55
+ type="password"
56
+ id="confirmPassword"
57
+ name="confirmPassword"
58
+ placeholder="••••••••"
59
+ class="w-full px-4 py-3 bg-slate-800/50 border border-white/10 rounded-xl text-white placeholder:text-slate-500 focus:outline-none focus:border-indigo-500 transition"
60
+ required
61
+ />
62
+ </div>
63
+
64
+ <button
65
+ type="submit"
66
+ class="w-full px-4 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-xl font-medium transition"
67
+ >
68
+ Đăng ký
69
+ </button>
70
+ </form>
71
+
72
+ <div class="text-center text-sm text-slate-400 mt-6">
73
+ Đã có tài khoản?
74
+ <a href="${pageContext.request.contextPath}/login" class="text-indigo-400 hover:text-indigo-300 font-medium">
75
+ Đăng nhập
76
+ </a>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <%@ include file="/WEB-INF/views/layouts/footer.jsp" %>
src/main/webapp/WEB-INF/views/dashBoard/employees/dashboard.jsp ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+
4
+ <%-- Set page variables --%>
5
+ <c:set var="pageTitle" value="Dashboard" scope="request"/>
6
+ <c:set var="roleColor" value="from-indigo-600 to-purple-600" scope="request"/>
7
+ <c:set var="roleLabel" value="Nhân viên" scope="request"/>
8
+ <c:set var="userName" value="Nguyễn Văn A" scope="request"/>
9
+ <c:set var="currentPage" value="/employee/dashboard" scope="request"/>
10
+
11
+ <%-- Define menu items --%>
12
+ <jsp:useBean id="menuItems" class="java.util.ArrayList" scope="request"/>
13
+ <%-- In real app, this would be populated from backend --%>
14
+
15
+ <%@ include file="/WEB-INF/views/layout/header.jsp" %>
16
+
17
+ <div class="min-h-screen bg-slate-950 flex">
18
+ <%@ include file="/WEB-INF/views/layout/sidebar.jsp" %>
19
+
20
+ <div class="flex-1 flex flex-col">
21
+ <header class="h-16 bg-slate-900/50 backdrop-blur-xl border-b border-white/10 flex items-center justify-between px-6">
22
+ <h1 class="text-xl font-bold text-white">Dashboard</h1>
23
+ <button class="relative text-slate-400 hover:text-white">
24
+ <i data-lucide="bell" class="w-5 h-5"></i>
25
+ <span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
26
+ </button>
27
+ </header>
28
+
29
+ <main class="flex-1 overflow-auto p-6">
30
+ <div class="space-y-6">
31
+ <!-- Welcome -->
32
+ <div class="bg-gradient-to-br from-indigo-600/20 to-purple-600/20 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
33
+ <h2 class="text-2xl font-bold text-white mb-2">Xin chào, ${userName}!</h2>
34
+ <p class="text-slate-300">Chúc bạn một ngày làm việc hiệu quả</p>
35
+ </div>
36
+
37
+ <!-- Stats Cards -->
38
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
39
+ <div class="bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
40
+ <div class="flex items-start justify-between">
41
+ <div class="flex-1">
42
+ <p class="text-sm text-slate-400 mb-1">Check In hôm nay</p>
43
+ <p class="text-2xl font-bold text-white mb-1">08:01 AM</p>
44
+ <p class="text-xs text-green-400">+2 phút sớm</p>
45
+ </div>
46
+ <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-green-600 to-emerald-600 flex items-center justify-center">
47
+ <i data-lucide="log-in" class="w-6 h-6 text-white"></i>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
53
+ <div class="flex items-start justify-between">
54
+ <div class="flex-1">
55
+ <p class="text-sm text-slate-400 mb-1">Giờ làm việc</p>
56
+ <p class="text-2xl font-bold text-white mb-1">7h 23m</p>
57
+ </div>
58
+ <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-600 to-cyan-600 flex items-center justify-center">
59
+ <i data-lucide="clock" class="w-6 h-6 text-white"></i>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
65
+ <div class="flex items-start justify-between">
66
+ <div class="flex-1">
67
+ <p class="text-sm text-slate-400 mb-1">Tỷ lệ chuyên cần</p>
68
+ <p class="text-2xl font-bold text-white mb-1">96%</p>
69
+ <p class="text-xs text-green-400">+2% so với tháng trước</p>
70
+ </div>
71
+ <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-600 to-pink-600 flex items-center justify-center">
72
+ <i data-lucide="trending-up" class="w-6 h-6 text-white"></i>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
78
+ <div class="flex items-start justify-between">
79
+ <div class="flex-1">
80
+ <p class="text-sm text-slate-400 mb-1">Yêu cầu chờ duyệt</p>
81
+ <p class="text-2xl font-bold text-white mb-1">2</p>
82
+ <p class="text-xs text-slate-400">1 nghỉ phép, 1 báo cáo</p>
83
+ </div>
84
+ <div class="w-12 h-12 rounded-xl bg-gradient-to-br from-orange-600 to-red-600 flex items-center justify-center">
85
+ <i data-lucide="alert-circle" class="w-6 h-6 text-white"></i>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+ <!-- Timeline & Calendar -->
92
+ <div class="grid lg:grid-cols-3 gap-6">
93
+ <div class="lg:col-span-2 bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
94
+ <h3 class="text-white font-bold mb-6 flex items-center gap-2">
95
+ <i data-lucide="clock" class="w-5 h-5"></i>
96
+ Lịch trình hôm nay
97
+ </h3>
98
+ <div class="space-y-4">
99
+ <div class="flex items-center gap-4">
100
+ <div class="w-10 h-10 rounded-full bg-green-600 flex items-center justify-center">
101
+ <i data-lucide="log-in" class="w-5 h-5 text-white"></i>
102
+ </div>
103
+ <div class="flex-1">
104
+ <div class="text-white font-medium">Check In</div>
105
+ <div class="text-sm text-slate-400">08:01</div>
106
+ </div>
107
+ <div class="text-xs bg-green-600/20 text-green-400 px-2 py-1 rounded">Hoàn thành</div>
108
+ </div>
109
+ <div class="flex items-center gap-4">
110
+ <div class="w-10 h-10 rounded-full bg-slate-700 flex items-center justify-center">
111
+ <i data-lucide="coffee" class="w-5 h-5 text-white"></i>
112
+ </div>
113
+ <div class="flex-1">
114
+ <div class="text-white font-medium">Nghỉ trưa</div>
115
+ <div class="text-sm text-slate-400">12:00</div>
116
+ </div>
117
+ </div>
118
+ <div class="flex items-center gap-4">
119
+ <div class="w-10 h-10 rounded-full bg-slate-700 flex items-center justify-center">
120
+ <i data-lucide="coffee" class="w-5 h-5 text-white"></i>
121
+ </div>
122
+ <div class="flex-1">
123
+ <div class="text-white font-medium">Quay lại làm việc</div>
124
+ <div class="text-sm text-slate-400">13:00</div>
125
+ </div>
126
+ </div>
127
+ <div class="flex items-center gap-4">
128
+ <div class="w-10 h-10 rounded-full bg-slate-700 flex items-center justify-center">
129
+ <i data-lucide="log-out" class="w-5 h-5 text-white"></i>
130
+ </div>
131
+ <div class="flex-1">
132
+ <div class="text-white font-medium">Check Out</div>
133
+ <div class="text-sm text-slate-400">17:30</div>
134
+ </div>
135
+ </div>
136
+ <button class="w-full px-4 py-3 mt-4 border border-white/20 text-white hover:bg-white/5 rounded-xl transition">
137
+ <i data-lucide="log-out" class="w-5 h-5 inline mr-2"></i>
138
+ Điểm danh ra ca
139
+ </button>
140
+ </div>
141
+ </div>
142
+
143
+ <div class="bg-slate-900/50 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
144
+ <h3 class="text-white font-bold mb-6 flex items-center gap-2">
145
+ <i data-lucide="calendar" class="w-5 h-5"></i>
146
+ Lịch làm việc
147
+ </h3>
148
+ <div class="space-y-4 mt-6">
149
+ <div class="flex items-center gap-2 text-sm">
150
+ <div class="w-3 h-3 rounded-full bg-green-500"></div>
151
+ <span class="text-slate-400">Có mặt</span>
152
+ </div>
153
+ <div class="flex items-center gap-2 text-sm">
154
+ <div class="w-3 h-3 rounded-full bg-yellow-500"></div>
155
+ <span class="text-slate-400">Đi muộn</span>
156
+ </div>
157
+ <div class="flex items-center gap-2 text-sm">
158
+ <div class="w-3 h-3 rounded-full bg-red-500"></div>
159
+ <span class="text-slate-400">Vắng mặt</span>
160
+ </div>
161
+ <div class="flex items-center gap-2 text-sm">
162
+ <div class="w-3 h-3 rounded-full bg-blue-500"></div>
163
+ <span class="text-slate-400">Nghỉ phép</span>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </main>
170
+ </div>
171
+ </div>
172
+
173
+ <%@ include file="/WEB-INF/views/layouts/footer.jsp" %>
src/main/webapp/WEB-INF/views/home.jsp ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+ <%@ include file="/WEB-INF/views/layouts/header.jsp" %>
4
+
5
+ <div class="min-h-screen bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-950">
6
+ <!-- Navigation -->
7
+ <nav class="border-b border-white/10 bg-slate-950/50 backdrop-blur-xl">
8
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
9
+ <div class="flex justify-between items-center h-16">
10
+ <div class="flex items-center gap-2">
11
+ <div class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center">
12
+ <i data-lucide="camera" class="w-6 h-6 text-white"></i>
13
+ </div>
14
+ <span class="text-xl font-bold bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
15
+ Smart Attendance
16
+ </span>
17
+ </div>
18
+ <div class="flex gap-4">
19
+ <a href="${pageContext.request.contextPath}/login" class="px-4 py-2 text-slate-300 hover:text-white transition">Đăng nhập</a>
20
+ <a href="${pageContext.request.contextPath}/register" class="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-lg transition">Bắt đầu ngay</a>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </nav>
25
+
26
+ <!-- Hero Section -->
27
+ <section class="relative overflow-hidden py-20 lg:py-32">
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
29
+ <div class="grid lg:grid-cols-2 gap-12 items-center">
30
+ <div>
31
+ <h1 class="text-5xl lg:text-7xl font-bold mb-6">
32
+ <span class="bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
33
+ SMART ATTENDANCE
34
+ </span>
35
+ <br />
36
+ <span class="text-white">SYSTEM</span>
37
+ </h1>
38
+ <p class="text-xl text-slate-300 mb-8 leading-relaxed">
39
+ Giải pháp điểm danh Face ID thông minh cho doanh nghiệp và cơ sở giáo dục.
40
+ Quản lý hiệu quả, chính xác và an toàn.
41
+ </p>
42
+ <div class="flex gap-4">
43
+ <a href="${pageContext.request.contextPath}/register" class="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-lg font-medium transition">
44
+ Bắt đầu ngay
45
+ </a>
46
+ <a href="${pageContext.request.contextPath}/login" class="px-6 py-3 border border-white/20 text-white hover:bg-white/10 rounded-lg font-medium transition">
47
+ Xem Demo
48
+ </a>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="relative">
53
+ <div class="bg-gradient-to-br from-indigo-600/20 to-purple-600/20 backdrop-blur-xl border border-white/10 rounded-3xl p-8">
54
+ <div class="grid grid-cols-2 gap-4">
55
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
56
+ <i data-lucide="users" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
57
+ <div class="text-3xl font-bold text-white mb-1">5000+</div>
58
+ <div class="text-sm text-slate-400">Người dùng</div>
59
+ </div>
60
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
61
+ <i data-lucide="target" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
62
+ <div class="text-3xl font-bold text-white mb-1">99.8%</div>
63
+ <div class="text-sm text-slate-400">Độ chính xác</div>
64
+ </div>
65
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
66
+ <i data-lucide="building-2" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
67
+ <div class="text-3xl font-bold text-white mb-1">50+</div>
68
+ <div class="text-sm text-slate-400">Đơn vị</div>
69
+ </div>
70
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
71
+ <i data-lucide="clock" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
72
+ <div class="text-3xl font-bold text-white mb-1">24/7</div>
73
+ <div class="text-sm text-slate-400">Hoạt động</div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </section>
81
+
82
+ <!-- Features Section -->
83
+ <section class="py-20 bg-slate-900/50">
84
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
85
+ <div class="text-center mb-16">
86
+ <h2 class="text-4xl font-bold text-white mb-4">Tính năng nổi bật</h2>
87
+ <p class="text-xl text-slate-400">Giải pháp toàn diện cho quản lý điểm danh</p>
88
+ </div>
89
+ <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
90
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
91
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-indigo-600 to-indigo-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
92
+ <i data-lucide="camera" class="w-7 h-7 text-white"></i>
93
+ </div>
94
+ <h3 class="text-xl font-bold text-white mb-2">Face Recognition</h3>
95
+ <p class="text-slate-400 leading-relaxed">Nhận diện khuôn mặt chính xác với AI, chống điểm danh hộ</p>
96
+ </div>
97
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
98
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-600 to-purple-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
99
+ <i data-lucide="bar-chart-3" class="w-7 h-7 text-white"></i>
100
+ </div>
101
+ <h3 class="text-xl font-bold text-white mb-2">Analytics Dashboard</h3>
102
+ <p class="text-slate-400 leading-relaxed">Thống kê realtime, biểu đồ trực quan, báo cáo chi tiết</p>
103
+ </div>
104
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
105
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-600 to-pink-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
106
+ <i data-lucide="file-text" class="w-7 h-7 text-white"></i>
107
+ </div>
108
+ <h3 class="text-xl font-bold text-white mb-2">Report Management</h3>
109
+ <p class="text-slate-400 leading-relaxed">Quản lý báo cáo công việc hằng ngày và cuối tuần</p>
110
+ </div>
111
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
112
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-orange-600 to-orange-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
113
+ <i data-lucide="calendar" class="w-7 h-7 text-white"></i>
114
+ </div>
115
+ <h3 class="text-xl font-bold text-white mb-2">Leave Management</h3>
116
+ <p class="text-slate-400 leading-relaxed">Xin nghỉ phép, duyệt đơn, theo dõi lịch sử nghỉ</p>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </section>
121
+
122
+ <!-- Footer -->
123
+ <footer class="border-t border-white/10 bg-slate-950/50 backdrop-blur-xl py-12">
124
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-slate-400">
125
+ <p>&copy; 2026 Smart Attendance System. All rights reserved.</p>
126
+ </div>
127
+ </footer>
128
+ </div>
129
+
130
+ <%@ include file="/WEB-INF/views/layouts/footer.jsp" %>
src/main/webapp/WEB-INF/views/landing.jsp ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+ <%@ include file="/WEB-INF/views/layout/header.jsp" %>
4
+
5
+ <div class="min-h-screen bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-950">
6
+ <!-- Navigation -->
7
+ <nav class="border-b border-white/10 bg-slate-950/50 backdrop-blur-xl">
8
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
9
+ <div class="flex justify-between items-center h-16">
10
+ <div class="flex items-center gap-2">
11
+ <div class="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center">
12
+ <i data-lucide="camera" class="w-6 h-6 text-white"></i>
13
+ </div>
14
+ <span class="text-xl font-bold bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
15
+ Smart Attendance
16
+ </span>
17
+ </div>
18
+ <div class="flex gap-4">
19
+ <a href="${pageContext.request.contextPath}/login" class="px-4 py-2 text-slate-300 hover:text-white transition">Đăng nhập</a>
20
+ <a href="${pageContext.request.contextPath}/register" class="px-4 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-lg transition">Bắt đầu ngay</a>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </nav>
25
+
26
+ <!-- Hero Section -->
27
+ <section class="relative overflow-hidden py-20 lg:py-32">
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
29
+ <div class="grid lg:grid-cols-2 gap-12 items-center">
30
+ <div>
31
+ <h1 class="text-5xl lg:text-7xl font-bold mb-6">
32
+ <span class="bg-gradient-to-r from-indigo-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
33
+ SMART ATTENDANCE
34
+ </span>
35
+ <br />
36
+ <span class="text-white">SYSTEM</span>
37
+ </h1>
38
+ <p class="text-xl text-slate-300 mb-8 leading-relaxed">
39
+ Giải pháp điểm danh Face ID thông minh cho doanh nghiệp và cơ sở giáo dục.
40
+ Quản lý hiệu quả, chính xác và an toàn.
41
+ </p>
42
+ <div class="flex gap-4">
43
+ <a href="${pageContext.request.contextPath}/register" class="px-6 py-3 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 rounded-lg font-medium transition">
44
+ Bắt đầu ngay
45
+ </a>
46
+ <a href="${pageContext.request.contextPath}/login" class="px-6 py-3 border border-white/20 text-white hover:bg-white/10 rounded-lg font-medium transition">
47
+ Xem Demo
48
+ </a>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="relative">
53
+ <div class="bg-gradient-to-br from-indigo-600/20 to-purple-600/20 backdrop-blur-xl border border-white/10 rounded-3xl p-8">
54
+ <div class="grid grid-cols-2 gap-4">
55
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
56
+ <i data-lucide="users" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
57
+ <div class="text-3xl font-bold text-white mb-1">5000+</div>
58
+ <div class="text-sm text-slate-400">Người dùng</div>
59
+ </div>
60
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
61
+ <i data-lucide="target" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
62
+ <div class="text-3xl font-bold text-white mb-1">99.8%</div>
63
+ <div class="text-sm text-slate-400">Độ chính xác</div>
64
+ </div>
65
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
66
+ <i data-lucide="building-2" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
67
+ <div class="text-3xl font-bold text-white mb-1">50+</div>
68
+ <div class="text-sm text-slate-400">Đơn vị</div>
69
+ </div>
70
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 rounded-2xl p-6 text-center">
71
+ <i data-lucide="clock" class="w-8 h-8 mx-auto mb-3 text-indigo-400"></i>
72
+ <div class="text-3xl font-bold text-white mb-1">24/7</div>
73
+ <div class="text-sm text-slate-400">Hoạt động</div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </section>
81
+
82
+ <!-- Features Section -->
83
+ <section class="py-20 bg-slate-900/50">
84
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
85
+ <div class="text-center mb-16">
86
+ <h2 class="text-4xl font-bold text-white mb-4">Tính năng nổi bật</h2>
87
+ <p class="text-xl text-slate-400">Giải pháp toàn diện cho quản lý điểm danh</p>
88
+ </div>
89
+ <div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
90
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
91
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-indigo-600 to-indigo-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
92
+ <i data-lucide="camera" class="w-7 h-7 text-white"></i>
93
+ </div>
94
+ <h3 class="text-xl font-bold text-white mb-2">Face Recognition</h3>
95
+ <p class="text-slate-400 leading-relaxed">Nhận diện khuôn mặt chính xác với AI, chống điểm danh hộ</p>
96
+ </div>
97
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
98
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-600 to-purple-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
99
+ <i data-lucide="bar-chart-3" class="w-7 h-7 text-white"></i>
100
+ </div>
101
+ <h3 class="text-xl font-bold text-white mb-2">Analytics Dashboard</h3>
102
+ <p class="text-slate-400 leading-relaxed">Thống kê realtime, biểu đồ trực quan, báo cáo chi tiết</p>
103
+ </div>
104
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
105
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-pink-600 to-pink-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
106
+ <i data-lucide="file-text" class="w-7 h-7 text-white"></i>
107
+ </div>
108
+ <h3 class="text-xl font-bold text-white mb-2">Report Management</h3>
109
+ <p class="text-slate-400 leading-relaxed">Quản lý báo cáo công việc hằng ngày và cuối tuần</p>
110
+ </div>
111
+ <div class="bg-slate-900/50 backdrop-blur-sm border border-white/10 hover:border-white/20 rounded-2xl p-6 transition-all group">
112
+ <div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-orange-600 to-orange-800 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
113
+ <i data-lucide="calendar" class="w-7 h-7 text-white"></i>
114
+ </div>
115
+ <h3 class="text-xl font-bold text-white mb-2">Leave Management</h3>
116
+ <p class="text-slate-400 leading-relaxed">Xin nghỉ phép, duyệt đơn, theo dõi lịch sử nghỉ</p>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </section>
121
+
122
+ <!-- Footer -->
123
+ <footer class="border-t border-white/10 bg-slate-950/50 backdrop-blur-xl py-12">
124
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-slate-400">
125
+ <p>&copy; 2026 Smart Attendance System. All rights reserved.</p>
126
+ </div>
127
+ </footer>
128
+ </div>
129
+
130
+ <%@ include file="/WEB-INF/views/layout/footer.jsp" %>
src/main/webapp/WEB-INF/views/layouts/base.jsp ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+
4
+ <%@ include file="/WEB-INF/views/layouts/header.jsp" %>
5
+
6
+ <div class="min-h-screen bg-slate-950 flex">
7
+ <!-- Sidebar -->
8
+ <%@ include file="/WEB-INF/views/layouts/sidebar.jsp" %>
9
+
10
+ <!-- Main Content -->
11
+ <div class="flex-1 flex flex-col">
12
+ <!-- Header -->
13
+ <header class="h-16 bg-slate-900/50 backdrop-blur-xl border-b border-white/10 flex items-center justify-between px-6">
14
+ <div>
15
+ <h1 class="text-xl font-bold text-white">${pageTitle}</h1>
16
+ </div>
17
+ <div class="flex items-center gap-4">
18
+ <button class="relative text-slate-400 hover:text-white">
19
+ <i data-lucide="bell" class="w-5 h-5"></i>
20
+ <span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
21
+ </button>
22
+ </div>
23
+ </header>
24
+
25
+ <!-- Page Content -->
26
+ <main class="flex-1 overflow-auto p-6">
27
+ <jsp:include page="${contentPage}" />
28
+ </main>
29
+ </div>
30
+ </div>
31
+
32
+ <%@ include file="/WEB-INF/views/layouts/footer.jsp" %>
src/main/webapp/WEB-INF/views/layouts/footer.jsp ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+
3
+ <!-- Custom JavaScript -->
4
+ <script src="${pageContext.request.contextPath}/js/main.js"></script>
5
+ <script src="${pageContext.request.contextPath}/js/face-recognition.js"></script>
6
+ <script src="${pageContext.request.contextPath}/js/attendance.js"></script>
7
+
8
+ <!-- Initialize Lucide Icons -->
9
+ <script>
10
+ lucide.createIcons();
11
+ </script>
12
+ </body>
13
+ </html>
src/main/webapp/WEB-INF/views/layouts/header.jsp ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+ <!DOCTYPE html>
4
+ <html lang="vi">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>${pageTitle != null ? pageTitle : 'Smart Attendance System'}</title>
9
+
10
+ <!-- Tailwind CSS -->
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+
13
+ <!-- Custom CSS -->
14
+ <link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
15
+ <link rel="stylesheet" href="${pageContext.request.contextPath}/css/dashboard.css">
16
+ <link rel="stylesheet" href="${pageContext.request.contextPath}/css/components.css">
17
+
18
+ <!-- Lucide Icons -->
19
+ <script src="https://unpkg.com/lucide@latest"></script>
20
+
21
+ <!-- Chart.js for charts -->
22
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
23
+
24
+ <!-- Tailwind Config -->
25
+ <script>
26
+ tailwind.config = {
27
+ darkMode: 'class',
28
+ theme: {
29
+ extend: {
30
+ colors: {
31
+ slate: {
32
+ 950: '#0f172a'
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ </script>
39
+ </head>
40
+ <body class="bg-slate-950 text-white">
src/main/webapp/WEB-INF/views/layouts/sidebar.jsp ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3
+
4
+ <aside class="w-64 bg-slate-900/50 backdrop-blur-xl border-r border-white/10 flex flex-col">
5
+ <!-- Logo -->
6
+ <div class="p-6 border-b border-white/10">
7
+ <div class="flex items-center gap-3">
8
+ <div class="w-10 h-10 bg-gradient-to-br ${roleColor} rounded-xl flex items-center justify-center">
9
+ <i data-lucide="camera" class="w-6 h-6 text-white"></i>
10
+ </div>
11
+ <div>
12
+ <div class="font-bold text-white">Smart Attendance</div>
13
+ <div class="text-xs text-slate-400">${roleLabel}</div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+
18
+ <!-- Menu -->
19
+ <nav class="flex-1 p-4 space-y-1">
20
+ <c:forEach items="${menuItems}" var="item">
21
+ <a href="${pageContext.request.contextPath}${item.path}"
22
+ class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${currentPage == item.path ? 'bg-gradient-to-r ' + roleColor + ' text-white' : 'text-slate-400 hover:text-white hover:bg-white/5'}">
23
+ <i data-lucide="${item.icon}" class="w-5 h-5"></i>
24
+ <span>${item.label}</span>
25
+ </a>
26
+ </c:forEach>
27
+ </nav>
28
+
29
+ <!-- User Profile -->
30
+ <div class="p-4 border-t border-white/10">
31
+ <div class="flex items-center gap-3 p-3 rounded-xl bg-slate-800/50">
32
+ <div class="w-10 h-10 rounded-full bg-gradient-to-br ${roleColor} flex items-center justify-center text-white font-bold">
33
+ ${userName.substring(0, 1)}
34
+ </div>
35
+ <div class="flex-1 min-w-0">
36
+ <div class="text-sm font-medium text-white truncate">${userName}</div>
37
+ <div class="text-xs text-slate-400">${roleLabel}</div>
38
+ </div>
39
+ </div>
40
+ <a href="${pageContext.request.contextPath}/login"
41
+ class="flex items-center gap-2 w-full px-4 py-2 mt-2 text-slate-400 hover:text-white rounded-xl hover:bg-white/5 transition-all">
42
+ <i data-lucide="log-out" class="w-4 h-4"></i>
43
+ <span>Đăng xuất</span>
44
+ </a>
45
+ </div>
46
+ </aside>
src/main/webapp/WEB-INF/views/webapp1/face-attendance.jsp ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <!DOCTYPE html>
3
+ <html lang="vi">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Điểm Danh Face ID - Face ID Attendance</title>
8
+ <link rel="stylesheet" href="css/theme.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+ <style>
11
+ .camera-frame {
12
+ position: relative;
13
+ width: 400px;
14
+ height: 500px;
15
+ background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
16
+ border-radius: 20px;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ overflow: hidden;
21
+ }
22
+ .face-icon {
23
+ font-size: 10rem;
24
+ opacity: 0.3;
25
+ }
26
+ .corner-marker {
27
+ position: absolute;
28
+ width: 30px;
29
+ height: 30px;
30
+ border: 3px solid #60a5fa;
31
+ }
32
+ .corner-marker.top-left { top: 20px; left: 20px; border-right: none; border-bottom: none; }
33
+ .corner-marker.top-right { top: 20px; right: 20px; border-left: none; border-bottom: none; }
34
+ .corner-marker.bottom-left { bottom: 20px; left: 20px; border-right: none; border-top: none; }
35
+ .corner-marker.bottom-right { bottom: 20px; right: 20px; border-left: none; border-top: none; }
36
+ .scan-line {
37
+ position: absolute;
38
+ width: 90%;
39
+ height: 2px;
40
+ background: linear-gradient(to right, transparent, #60a5fa, transparent);
41
+ animation: scan 2s linear infinite;
42
+ }
43
+ @keyframes scan {
44
+ 0% { top: 0%; }
45
+ 100% { top: 100%; }
46
+ }
47
+ .log-item {
48
+ padding: 1rem;
49
+ background: var(--card);
50
+ border: 1px solid var(--border);
51
+ border-radius: 0.5rem;
52
+ margin-bottom: 0.5rem;
53
+ }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ <div class="header">
58
+ <div class="nav">
59
+ <div class="nav-brand">
60
+ <svg width="32" height="32" fill="none" stroke="currentColor" stroke-width="2">
61
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
62
+ <circle cx="12" cy="7" r="4"></circle>
63
+ </svg>
64
+ Điểm Danh Face ID
65
+ </div>
66
+ <div class="flex items-center gap-4">
67
+ <div class="clock text-right">
68
+ <div class="clock-time font-bold text-xl">--:--:--</div>
69
+ <div class="clock-date text-sm text-muted"></div>
70
+ </div>
71
+ <button id="theme-toggle" onclick="AttendanceSystem.toggleTheme()" class="btn btn-icon btn-outline">
72
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
73
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
74
+ </svg>
75
+ </button>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="container" style="padding: 2rem 1.5rem;">
81
+ <div class="grid grid-cols-3 gap-6">
82
+ <!-- Recent Logs -->
83
+ <div>
84
+ <h3 class="font-bold mb-4">Lịch sử gần đây</h3>
85
+ <div style="max-height: 600px; overflow-y: auto;">
86
+ <div class="log-item animate-slide-up">
87
+ <div class="flex items-center gap-3">
88
+ <div class="badge badge-success">✓</div>
89
+ <div>
90
+ <div class="font-semibold">Nguyễn Văn A</div>
91
+ <div class="text-xs text-muted">Check In: 07:58</div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <div class="log-item animate-slide-up">
96
+ <div class="flex items-center gap-3">
97
+ <div class="badge badge-warning">!</div>
98
+ <div>
99
+ <div class="font-semibold">Trần Thị B</div>
100
+ <div class="text-xs text-muted">Check In: 08:12 (Muộn)</div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ <div class="log-item animate-slide-up">
105
+ <div class="flex items-center gap-3">
106
+ <div class="badge badge-success">✓</div>
107
+ <div>
108
+ <div class="font-semibold">Lê Văn C</div>
109
+ <div class="text-xs text-muted">Check In: 08:00</div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Camera View -->
117
+ <div class="text-center">
118
+ <h3 class="font-bold mb-4">Quét khuôn mặt</h3>
119
+ <div class="camera-frame mx-auto">
120
+ <div class="face-icon">👤</div>
121
+ <div class="corner-marker top-left"></div>
122
+ <div class="corner-marker top-right"></div>
123
+ <div class="corner-marker bottom-left"></div>
124
+ <div class="corner-marker bottom-right"></div>
125
+ <div class="scan-line"></div>
126
+ </div>
127
+ <p class="text-muted mt-4">Vui lòng nhìn vào camera để điểm danh</p>
128
+ <p class="text-sm text-muted">Hệ thống đang quét...</p>
129
+ </div>
130
+
131
+ <!-- Stats -->
132
+ <div>
133
+ <h3 class="font-bold mb-4">Thống kê hôm nay</h3>
134
+ <div class="stat-card stat-card-success mb-4">
135
+ <div class="text-muted text-sm mb-1">Đã điểm danh</div>
136
+ <div class="text-3xl font-bold text-success">42</div>
137
+ </div>
138
+ <div class="stat-card stat-card-warning mb-4">
139
+ <div class="text-muted text-sm mb-1">Đi muộn</div>
140
+ <div class="text-3xl font-bold text-warning">8</div>
141
+ </div>
142
+ <div class="stat-card stat-card-destructive mb-4">
143
+ <div class="text-muted text-sm mb-1">Chưa điểm danh</div>
144
+ <div class="text-3xl font-bold text-destructive">12</div>
145
+ </div>
146
+ <div class="card mt-6">
147
+ <div class="text-sm text-muted mb-2">Tổng nhân viên</div>
148
+ <div class="text-2xl font-bold">50</div>
149
+ <div class="text-xs text-muted mt-2">Tỷ lệ chuyên cần: 84%</div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <script src="js/main.js"></script>
156
+ </body>
157
+ </html>
src/main/webapp/WEB-INF/views/webapp1/index.jsp ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <!DOCTYPE html>
3
+ <html lang="vi">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Hệ Thống Điểm Danh Face ID</title>
8
+ <link rel="stylesheet" href="css/theme.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+ </head>
11
+ <body>
12
+ <div class="min-h-screen flex flex-col items-center justify-center" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
13
+ <div class="text-center" style="color: white; max-width: 600px; padding: 2rem;">
14
+ <h1 style="font-size: 3rem; margin-bottom: 1rem;">🎯 Hệ Thống Điểm Danh</h1>
15
+ <h2 style="font-size: 2rem; margin-bottom: 2rem;">Face ID Recognition</h2>
16
+ <p style="font-size: 1.25rem; margin-bottom: 3rem; opacity: 0.9;">
17
+ Hệ thống điểm danh tự động bằng nhận diện khuôn mặt,
18
+ quản lý báo cáo và nghỉ phép thông minh
19
+ </p>
20
+
21
+ <div class="grid grid-cols-1 gap-4" style="max-width: 400px; margin: 0 auto;">
22
+ <a href="face-attendance.jsp" class="btn btn-lg" style="background: white; color: #667eea; font-size: 1.25rem; padding: 1rem 2rem;">
23
+ <svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; margin-right: 0.5rem;">
24
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
25
+ <circle cx="12" cy="7" r="4"></circle>
26
+ </svg>
27
+ Điểm Danh Face ID
28
+ </a>
29
+
30
+ <a href="login.jsp" class="btn btn-lg btn-outline" style="background: rgba(255,255,255,0.2); color: white; border: 2px solid white; font-size: 1.25rem; padding: 1rem 2rem;">
31
+ <svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; margin-right: 0.5rem;">
32
+ <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
33
+ <polyline points="10 17 15 12 10 7"></polyline>
34
+ <line x1="15" y1="12" x2="3" y2="12"></line>
35
+ </svg>
36
+ Đăng Nhập Hệ Thống
37
+ </a>
38
+
39
+ <a href="register.jsp" class="btn btn-lg" style="background: rgba(255,255,255,0.1); color: white; border: 1px solid rgba(255,255,255,0.3); font-size: 1rem; padding: 0.75rem 1.5rem;">
40
+ Đăng ký tài khoản mới →
41
+ </a>
42
+ </div>
43
+
44
+ <div class="mt-4" style="margin-top: 3rem; opacity: 0.8;">
45
+ <p style="font-size: 0.875rem;">© 2026 Face ID Attendance System. All rights reserved.</p>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <script src="js/main.js"></script>
51
+ </body>
52
+ </html>
src/main/webapp/WEB-INF/views/webapp1/login.jsp ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <!DOCTYPE html>
3
+ <html lang="vi">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Đăng Nhập - Face ID Attendance</title>
8
+ <link rel="stylesheet" href="css/theme.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+ </head>
11
+ <body>
12
+ <div class="min-h-screen flex items-center justify-center" style="background-color: var(--background); padding: 1.5rem;">
13
+ <div class="card" style="max-width: 450px; width: 100%;">
14
+ <div class="card-header text-center">
15
+ <h1 class="card-title" style="font-size: 2rem;">Đăng Nhập</h1>
16
+ <p class="card-description">Nhập thông tin để truy cập hệ thống</p>
17
+ </div>
18
+
19
+ <div class="card-body">
20
+ <% String error = request.getParameter("error");
21
+ if (error != null) { %>
22
+ <div class="alert alert-destructive mb-4">
23
+ Sai email hoặc mật khẩu!
24
+ </div>
25
+ <% } %>
26
+
27
+ <form action="login" method="post" id="loginForm">
28
+ <div class="form-group">
29
+ <label class="form-label">Vai trò</label>
30
+ <select name="role" class="form-control" required>
31
+ <option value="EMPLOYEE">Nhân viên (Employee)</option>
32
+ <option value="TEACHER">Giáo viên / Quản lý (Teacher)</option>
33
+ <option value="ADMIN">Quản trị viên (Admin)</option>
34
+ </select>
35
+ </div>
36
+
37
+ <div class="form-group">
38
+ <label class="form-label">Email</label>
39
+ <input type="email" name="email" class="form-control" placeholder="your.email@company.com" required>
40
+ </div>
41
+
42
+ <div class="form-group">
43
+ <label class="form-label">Mật khẩu</label>
44
+ <div style="position: relative;">
45
+ <input type="password" name="password" id="password" class="form-control" placeholder="••••••••" required>
46
+ <button type="button" onclick="AttendanceSystem.togglePassword('password')"
47
+ style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer;">
48
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
49
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
50
+ <circle cx="12" cy="12" r="3"></circle>
51
+ </svg>
52
+ </button>
53
+ </div>
54
+ </div>
55
+
56
+ <button type="submit" class="btn btn-primary w-full" style="width: 100%; margin-top: 1.5rem;">
57
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
58
+ <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
59
+ <polyline points="10 17 15 12 10 7"></polyline>
60
+ <line x1="15" y1="12" x2="3" y2="12"></line>
61
+ </svg>
62
+ Đăng nhập
63
+ </button>
64
+ </form>
65
+ </div>
66
+
67
+ <div class="card-footer text-center">
68
+ <p class="text-sm text-muted">
69
+ Chưa có tài khoản?
70
+ <a href="register.jsp" class="text-primary font-semibold">Đăng ký ngay</a>
71
+ </p>
72
+ <p class="text-sm text-muted" style="margin-top: 0.5rem;">
73
+ <a href="index.jsp" class="text-muted">← Về trang chủ</a>
74
+ </p>
75
+ </div>
76
+
77
+ <div style="text-align: center; margin-top: 1rem;">
78
+ <button id="theme-toggle" onclick="AttendanceSystem.toggleTheme()" class="btn btn-icon btn-outline">
79
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
80
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
81
+ </svg>
82
+ </button>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <script src="js/main.js"></script>
88
+ </body>
89
+ </html>
src/main/webapp/WEB-INF/views/webapp1/register.jsp ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2
+ <!DOCTYPE html>
3
+ <html lang="vi">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Đăng Ký - Face ID Attendance</title>
8
+ <link rel="stylesheet" href="css/theme.css">
9
+ <link rel="stylesheet" href="css/components.css">
10
+ </head>
11
+ <body>
12
+ <div class="min-h-screen flex items-center justify-center" style="background-color: var(--background); padding: 1.5rem;">
13
+ <div class="card" style="max-width: 500px; width: 100%;">
14
+ <div class="card-header text-center">
15
+ <h1 class="card-title" style="font-size: 2rem;">Đăng Ký Tài Khoản</h1>
16
+ <p class="card-description">Tạo tài khoản mới để sử dụng hệ thống</p>
17
+ </div>
18
+
19
+ <div class="card-body">
20
+ <form action="register" method="post" id="registerForm">
21
+ <div class="form-group">
22
+ <label class="form-label">Họ và tên *</label>
23
+ <input type="text" name="name" class="form-control" placeholder="Nguyễn Văn A" required>
24
+ </div>
25
+
26
+ <div class="form-group">
27
+ <label class="form-label">Email *</label>
28
+ <input type="email" name="email" class="form-control" placeholder="your.email@company.com" required>
29
+ </div>
30
+
31
+ <div class="form-group">
32
+ <label class="form-label">Số điện thoại *</label>
33
+ <input type="tel" name="phone" class="form-control" placeholder="0901234567" required>
34
+ </div>
35
+
36
+ <div class="form-group">
37
+ <label class="form-label">Phòng ban *</label>
38
+ <select name="department" class="form-control" required>
39
+ <option value="">Chọn phòng ban</option>
40
+ <option value="IT">IT</option>
41
+ <option value="HR">HR</option>
42
+ <option value="Marketing">Marketing</option>
43
+ <option value="Finance">Finance</option>
44
+ </select>
45
+ </div>
46
+
47
+ <div class="form-group">
48
+ <label class="form-label">Mật khẩu *</label>
49
+ <input type="password" name="password" id="password" class="form-control" placeholder="••••••••" required>
50
+ </div>
51
+
52
+ <div class="form-group">
53
+ <label class="form-label">Xác nhận mật khẩu *</label>
54
+ <input type="password" name="confirmPassword" class="form-control" placeholder="••••••••" required>
55
+ </div>
56
+
57
+ <div class="alert alert-info" style="font-size: 0.875rem; margin-top: 1rem;">
58
+ <strong>Lưu ý:</strong> Tài khoản của bạn sẽ cần được Admin phê duyệt trước khi có thể đăng nhập.
59
+ </div>
60
+
61
+ <button type="submit" class="btn btn-primary w-full" style="width: 100%; margin-top: 1.5rem;">
62
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
63
+ <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
64
+ <circle cx="8.5" cy="7" r="4"></circle>
65
+ <line x1="20" y1="8" x2="20" y2="14"></line>
66
+ <line x1="23" y1="11" x2="17" y2="11"></line>
67
+ </svg>
68
+ Đăng ký
69
+ </button>
70
+ </form>
71
+ </div>
72
+
73
+ <div class="card-footer text-center">
74
+ <p class="text-sm text-muted">
75
+ Đã có tài khoản?
76
+ <a href="login.jsp" class="text-primary font-semibold">Đăng nhập</a>
77
+ </p>
78
+ <p class="text-sm text-muted" style="margin-top: 0.5rem;">
79
+ <a href="index.jsp" class="text-muted">← Về trang chủ</a>
80
+ </p>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <script src="js/main.js"></script>
86
+ </body>
87
+ </html>
src/test/java/com/attendenceSystem/AttendenceSystemApplicationTests.java ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.attendenceSystem;
2
+
3
+ import org.junit.jupiter.api.Test;
4
+ import org.springframework.boot.test.context.SpringBootTest;
5
+
6
+ @SpringBootTest
7
+ class AttendenceSystemApplicationTests {
8
+
9
+ @Test
10
+ void contextLoads() {
11
+ }
12
+
13
+ }