Browse Source

first commit

master
yoga-agoy 4 months ago
commit
6e6ed71659
  1. 2
      .gitattributes
  2. 33
      .gitignore
  3. 3
      .mvn/wrapper/maven-wrapper.properties
  4. 295
      mvnw
  5. 189
      mvnw.cmd
  6. 97
      pom.xml
  7. 13
      src/main/java/com/gcc_manpower/GccManpowerApplication.java
  8. 30
      src/main/java/com/gcc_manpower/config/DataSourceConfig.java
  9. 559
      src/main/java/com/gcc_manpower/controller/HomeController.java
  10. 836
      src/main/java/com/gcc_manpower/service/AppUserService.java
  11. 37
      src/main/resources/application-local.properties
  12. 44
      src/main/resources/application-prod.properties
  13. 5
      src/main/resources/application.properties
  14. 11794
      src/main/resources/static/assets/bootstrap-5.3.2/css/bootstrap.min.css
  15. 2212
      src/main/resources/static/assets/bootstrap-5.3.2/js/bootstrap.min.js
  16. 193
      src/main/resources/static/assets/css/body-form-styles.css
  17. 114
      src/main/resources/static/assets/css/datatable-styles.css
  18. 77
      src/main/resources/static/assets/css/header-styles.css
  19. 826
      src/main/resources/static/assets/css/home-styles.css
  20. 272
      src/main/resources/static/assets/css/login-form-styles.css
  21. 532
      src/main/resources/static/assets/datatables-1.13.6/css/dataTables.bootstrap5.min.css
  22. 80
      src/main/resources/static/assets/datatables-1.13.6/js/dataTables.bootstrap5.min.js
  23. 2990
      src/main/resources/static/assets/datatables-1.13.6/js/jquery.dataTables.min.js
  24. 248
      src/main/resources/static/assets/datatables-buttons-2.4.1/css/buttons.bootstrap5.min.css
  25. 70
      src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.bootstrap5.min.js
  26. 538
      src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js
  27. 79
      src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.print.min.js
  28. 764
      src/main/resources/static/assets/datatables-buttons-2.4.1/js/dataTables.buttons.min.js
  29. 6995
      src/main/resources/static/assets/font-awesome-6.5.1/css/all.min.css
  30. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.ttf
  31. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.woff2
  32. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.ttf
  33. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.woff2
  34. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.ttf
  35. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.woff2
  36. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.ttf
  37. BIN
      src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.woff2
  38. 7023
      src/main/resources/static/assets/font-awesome-6.5.2/css/all.min.css
  39. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.ttf
  40. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.woff2
  41. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.ttf
  42. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.woff2
  43. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.ttf
  44. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.woff2
  45. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.ttf
  46. BIN
      src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.woff2
  47. BIN
      src/main/resources/static/assets/header-images/gcc1.png
  48. 3244
      src/main/resources/static/assets/jquery-3.6.4/js/jquery-3.6.4.min.js
  49. 93
      src/main/resources/static/assets/js/add-url-data-script.js
  50. 23
      src/main/resources/static/assets/js/fetch-csrf-script.js
  51. 3
      src/main/resources/static/assets/js/go-back-script.js
  52. 426
      src/main/resources/static/assets/js/home-script.js
  53. 28
      src/main/resources/static/assets/js/login-script.js
  54. 136
      src/main/resources/static/assets/js/update-url-data-script.js
  55. 56
      src/main/resources/static/assets/js/view-login-details-script.js
  56. 1123
      src/main/resources/static/assets/popper-2.11.8/js/popper.min.js
  57. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-100.woff2
  58. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-200.woff2
  59. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-300.woff2
  60. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-500.woff2
  61. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-600.woff2
  62. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2
  63. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-regular.woff2
  64. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-100.woff2
  65. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-200.woff2
  66. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-300.woff2
  67. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-500.woff2
  68. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-600.woff2
  69. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-700.woff2
  70. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-regular.woff2
  71. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-100.woff2
  72. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-200.woff2
  73. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-300.woff2
  74. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-500.woff2
  75. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-600.woff2
  76. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-700.woff2
  77. BIN
      src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-regular.woff2
  78. 21
      src/main/resources/templates/fragments/footer.html
  79. 22
      src/main/resources/templates/fragments/header.html
  80. 79
      src/main/resources/templates/fragments/navbar.html
  81. 3456
      src/main/resources/templates/home/home.html
  82. 88
      src/main/resources/templates/home/login.html
  83. 941
      src/main/resources/templates/home/view-login-details.html
  84. 30
      src/main/resources/templates/modals/login-modal.html

2
.gitattributes

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore

@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

3
.mvn/wrapper/maven-wrapper.properties

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip

295
mvnw

@ -0,0 +1,295 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

189
mvnw.cmd

@ -0,0 +1,189 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@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 @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
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."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

97
pom.xml

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.15-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gcc_manpower</groupId>
<artifactId>gcc-manpower</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name/>
<description/>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>

13
src/main/java/com/gcc_manpower/GccManpowerApplication.java

@ -0,0 +1,13 @@
package com.gcc_manpower;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GccManpowerApplication {
public static void main(String[] args) {
SpringApplication.run(GccManpowerApplication.class, args);
}
}

30
src/main/java/com/gcc_manpower/config/DataSourceConfig.java

@ -0,0 +1,30 @@
package com.gcc_manpower.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
private static String host = "localhost:3306";
private static String dbpassword = "root";
// AWS
// private static String host =
// "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
// private static String dbpassword = "gcc-facial-password";
@Bean(name = "mysqlManpowerDataSource")
public DataSource mysqlManpowerDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://" + host + "/gcc_manpower");
dataSource.setUsername("root");
dataSource.setPassword(dbpassword);
return dataSource;
}
}

559
src/main/java/com/gcc_manpower/controller/HomeController.java

@ -0,0 +1,559 @@
package com.gcc_manpower.controller;
import com.gcc_manpower.service.AppUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import jakarta.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/")
public class HomeController {
@Autowired
private AppUserService appUserService;
private void addSummaryStats(Model model, List<Map<String, Object>> vendorDetails) {
int totalVendors = vendorDetails.size();
int totalTenderCount = appUserService.getOverallTenderCount();
int totalExistingCount = appUserService.getOverallActiveEmployeeCount();
int totalVacantCount = 0;
for (Map<String, Object> vendor : vendorDetails) {
List<Map<String, Object>> designations = (List<Map<String, Object>>) vendor.get("designationDetails");
if (designations != null) {
for (Map<String, Object> des : designations) {
Object vacantObj = des.get("vacant_count");
if (vacantObj != null) {
try {
totalVacantCount += Integer.parseInt(String.valueOf(vacantObj).trim());
} catch (NumberFormatException ignored) {
}
}
}
}
}
model.addAttribute("totalVendors", totalVendors);
model.addAttribute("totalTenderCount", totalTenderCount);
model.addAttribute("totalExistingCount", totalExistingCount);
model.addAttribute("totalVacantCount", totalVacantCount);
}
@GetMapping("/")
public String home(Model model, HttpSession session) {
String usrName = (String) session.getAttribute("usrName");
if (usrName == null) {
return "redirect:/login";
}
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
List<Map<String, Object>> vendorDetails = appUserService.getVendorDetailsWithCounts();
addSummaryStats(model, vendorDetails);
model.addAttribute("curPage", "home");
model.addAttribute("isAdmin", isAdmin);
model.addAttribute("urls", new ArrayList<>()); // default empty services
model.addAttribute("vendorDetails", vendorDetails);
model.addAttribute("locations", appUserService.getActiveLocations());
model.addAttribute("designations", appUserService.getActiveDesignations());
model.addAttribute("userTypes", appUserService.getActiveUserTypes());
return "home/home";
}
@GetMapping("/login")
public String login(HttpSession session) {
// If already logged in, redirect to the home page
if (session.getAttribute("usrName") != null) {
return "redirect:/";
}
return "home/login";
}
@PostMapping("/api/val-app-usr-login")
@ResponseBody
public Map<String, Object> validateAppUserLogin(
@RequestParam("usrName") String usrName,
@RequestParam("usrPass") String usrPass,
HttpSession session) {
Map<String, Object> authResult = appUserService.authenticate(usrName, usrPass);
Map<String, Object> response = new HashMap<>();
if (Boolean.TRUE.equals(authResult.get("success"))) {
session.setAttribute("usrName", authResult.get("usrName"));
session.setAttribute("usrRole", authResult.get("usrRole"));
session.setMaxInactiveInterval(600); // 10 minutes session timeout
response.put("result", true);
response.put("usrName", authResult.get("usrName"));
} else {
response.put("result", false);
response.put("error", authResult.get("error"));
}
return response;
}
@PostMapping("/api/get-role")
@ResponseBody
public Map<String, Object> getRole(HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
if (usrName == null) {
response.put("authenticated", false);
response.put("isAdmin", false);
} else {
response.put("authenticated", true);
response.put("isAdmin", "ADMIN".equalsIgnoreCase(usrRole));
}
return response;
}
@GetMapping("/redirect")
public String redirect() {
return "redirect:/";
}
@GetMapping("/view-login-det")
public String viewLoginDetails(Model model, HttpSession session) {
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
if (usrName == null) {
return "redirect:/login";
}
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
// Fetch exact spreadsheet rows dynamically from MySQL using JdbcTemplate
List<Map<String, Object>> manpowerEntries = appUserService.getManpowerTenders();
List<Map<String, Object>> vendorDetails = appUserService.getVendorDetailsWithCounts();
model.addAttribute("curPage", "view-login-det");
model.addAttribute("isAdmin", isAdmin);
model.addAttribute("manpowerEntries", manpowerEntries);
model.addAttribute("vendorDetails", vendorDetails);
model.addAttribute("designations", appUserService.getActiveDesignations());
return "home/view-login-details";
}
@GetMapping("/api/vendor-employees")
@ResponseBody
public List<Map<String, Object>> getVendorEmployees(
@RequestParam("vendorName") String vendorName) {
return appUserService.getEmployeesByVendor(vendorName);
}
@GetMapping("/api/employee/history")
@ResponseBody
public List<Map<String, Object>> getEmployeeHistory(
@RequestParam("empCode") String empCode) {
return appUserService.getEmployeeHistory(empCode);
}
@GetMapping("/api/all-employees")
@ResponseBody
public List<Map<String, Object>> getAllEmployees() {
return appUserService.getAllEmployees();
}
@PostMapping("/api/save-manpower-stats")
@ResponseBody
public Map<String, Object> saveManpowerStats(
@RequestBody List<Map<String, Object>> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
if (usrName == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
try {
boolean overallSuccess = true;
for (Map<String, Object> row : payload) {
int id = ((Number) row.get("id")).intValue();
int availableCount = ((Number) row.get("availableCount")).intValue();
String vacantRemarks = (String) row.get("vacantRemarks");
boolean rowSuccess = appUserService.updateManpowerTender(id, availableCount, vacantRemarks);
if (!rowSuccess) {
overallSuccess = false;
}
}
response.put("success", overallSuccess);
} catch (Exception e) {
response.put("success", false);
response.put("error", "Failed to save statistics: " + e.getMessage());
}
return response;
}
@GetMapping("/api/employee")
@ResponseBody
public Map<String, Object> getEmployeeByCode(
@RequestParam("empCode") String empCode,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
if (usrName == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
Map<String, Object> emp = appUserService.getEmployeeByCode(empCode);
if (emp != null) {
response.put("success", true);
response.put("employee", emp);
} else {
response.put("success", false);
response.put("error", "Employee not found or inactive.");
}
return response;
}
@PostMapping("/api/employee/add")
@ResponseBody
public Map<String, Object> addEmployee(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
boolean success = appUserService.addEmployee(payload);
if (success) {
response.put("success", true);
response.put("message", "Employee registered successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to register employee. The employee code might already exist.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@PostMapping("/api/employee/update")
@ResponseBody
public Map<String, Object> updateEmployee(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
boolean success = appUserService.updateEmployee(payload);
if (success) {
response.put("success", true);
response.put("message", "Employee updated successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to update employee details. Record not found or inactive.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@PostMapping("/api/vendor/add-work-order")
@ResponseBody
public Map<String, Object> addVendorWorkOrder(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
String vendorType = (String) payload.get("vendorType");
String vendorIdStr = (String) payload.get("vendorId");
String vendorName = (String) payload.get("vendorName");
String description = (String) payload.get("description");
String contractorName = (String) payload.get("contractorName");
String fileNo = (String) payload.get("fileNo");
String subject = (String) payload.get("subject");
String issueDate = (String) payload.get("issueDate");
String endDate = (String) payload.get("endDate");
String totalEmployees = (String) payload.get("totalEmployees");
Integer orderby = null;
if (payload.get("orderby") != null) {
try {
orderby = ((Number) payload.get("orderby")).intValue();
} catch (Exception ignored) {
}
}
// Convert to uppercase for Contractor Name and File No
if (contractorName != null) {
contractorName = contractorName.toUpperCase();
}
if (fileNo != null) {
fileNo = fileNo.toUpperCase();
}
long finalVendorId = 0;
if ("new".equalsIgnoreCase(vendorType)) {
// Check if vendor name already exists
List<Map<String, Object>> existingVendors = appUserService.getVendorDetailsWithCounts();
boolean vendorExists = false;
for (Map<String, Object> v : existingVendors) {
String vName = (String) v.get("vendor_name");
if (vName != null && vName.equalsIgnoreCase(vendorName)) {
vendorExists = true;
break;
}
}
if (vendorExists) {
response.put("success", false);
response.put("error", "A vendor with this name already exists.");
return response;
}
// Insert into vendor_details
finalVendorId = appUserService.addVendor(vendorName, description, totalEmployees, orderby);
if (finalVendorId == 0) {
response.put("success", false);
response.put("error", "Failed to create new vendor details.");
return response;
}
} else {
if (vendorIdStr == null || vendorIdStr.trim().isEmpty()) {
response.put("success", false);
response.put("error", "No existing vendor selected.");
return response;
}
try {
finalVendorId = Long.parseLong(vendorIdStr);
} catch (NumberFormatException e) {
response.put("success", false);
response.put("error", "Invalid vendor ID formatting.");
return response;
}
}
// Insert into vendor_details_work_order
boolean success = appUserService.addVendorWorkOrder(
String.valueOf(finalVendorId),
description,
contractorName,
fileNo,
subject,
issueDate,
endDate,
totalEmployees,
orderby);
if (success) {
// If it's an existing vendor, add total_employees to vendor_details
if (!"new".equalsIgnoreCase(vendorType)) {
int addedEmployees = 0;
if (totalEmployees != null) {
try {
addedEmployees = Integer.parseInt(totalEmployees.trim());
} catch (NumberFormatException ignored) {
}
}
if (addedEmployees > 0) {
appUserService.updateVendorTotalEmployees(String.valueOf(finalVendorId), addedEmployees);
}
}
response.put("success", true);
response.put("message", "Vendor work order saved successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to register vendor work order.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@PostMapping("/api/employee/delete")
@ResponseBody
public Map<String, Object> deleteEmployee(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
String empCode = (String) payload.get("empCode");
if (empCode == null || empCode.trim().isEmpty()) {
response.put("success", false);
response.put("error", "Employee code is required.");
return response;
}
boolean success = appUserService.deleteEmployee(empCode);
if (success) {
response.put("success", true);
response.put("message", "Employee deleted successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to delete employee or record not found.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@PostMapping("/api/vendor/delete")
@ResponseBody
public Map<String, Object> deleteVendor(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
Object idObj = payload.get("id");
if (idObj == null) {
response.put("success", false);
response.put("error", "Vendor ID is required.");
return response;
}
String idStr = String.valueOf(idObj);
boolean success = appUserService.deleteVendor(idStr);
if (success) {
response.put("success", true);
response.put("message", "Vendor and associated records deleted successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to delete vendor or record not found.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@GetMapping("/api/vendor-employees-all")
@ResponseBody
public List<Map<String, Object>> getVendorEmployeesAll(
@RequestParam("vendorIdOrName") String vendorIdOrName) {
return appUserService.getEmployeesByVendorAll(vendorIdOrName);
}
@PostMapping("/api/employee/restore")
@ResponseBody
public Map<String, Object> restoreEmployee(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
String empCode = (String) payload.get("empCode");
if (empCode == null || empCode.trim().isEmpty()) {
response.put("success", false);
response.put("error", "Employee code is required.");
return response;
}
boolean success = appUserService.restoreEmployee(empCode);
if (success) {
response.put("success", true);
response.put("message", "Employee restored successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to restore employee or record not found.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@GetMapping("/api/all-employees-with-deleted")
@ResponseBody
public List<Map<String, Object>> getAllEmployeesWithDeleted() {
return appUserService.getAllEmployeesWithDeleted();
}
@PostMapping("/usr-logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/login";
}
}

836
src/main/java/com/gcc_manpower/service/AppUserService.java

@ -0,0 +1,836 @@
package com.gcc_manpower.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import jakarta.annotation.PostConstruct;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class AppUserService {
private JdbcTemplate jdbcManpowerTemplate;
@Autowired
public void setDataSource(@Qualifier("mysqlManpowerDataSource") DataSource manpowerDataSource) {
this.jdbcManpowerTemplate = new JdbcTemplate(manpowerDataSource);
}
public Map<String, Object> authenticate(String username, String password) {
Map<String, Object> result = new HashMap<>();
String sql = "SELECT usr_name, usr_pass, usr_role, is_active FROM app_user WHERE usr_name = ?";
try {
List<Map<String, Object>> users = jdbcManpowerTemplate.queryForList(sql, username);
if (users == null || users.isEmpty()) {
result.put("success", false);
result.put("error", "Invalid username or password!");
return result;
}
Map<String, Object> user = users.get(0);
// Check if user is active
int isActive = 1;
Object activeObj = user.get("is_active");
if (activeObj instanceof Number) {
isActive = ((Number) activeObj).intValue();
} else if (activeObj instanceof Boolean) {
isActive = (Boolean) activeObj ? 1 : 0;
}
if (isActive == 0) {
result.put("success", false);
result.put("error", "User account is inactive!");
return result;
}
String dbPass = (String) user.get("usr_pass");
boolean matches = verifyPassword(password, dbPass);
if (matches) {
result.put("success", true);
result.put("usrName", user.get("usr_name"));
result.put("usrRole", user.get("usr_role"));
} else {
result.put("success", false);
result.put("error", "Invalid username or password!");
}
} catch (Exception e) {
result.put("success", false);
result.put("error", "Database connection/query error: " + e.getMessage());
}
return result;
}
public List<Map<String, Object>> getManpowerTenders() {
String sql = "SELECT id, description, vendor_name, tender_count, available_count, working_location, registered_count, vacant_remarks FROM manpower_tender ORDER BY id";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
return new ArrayList<>();
}
}
public boolean updateManpowerTender(int id, int availableCount, String vacantRemarks) {
String sql = "UPDATE manpower_tender SET available_count = ?, vacant_remarks = ? WHERE id = ?";
try {
int rows = jdbcManpowerTemplate.update(sql, availableCount, vacantRemarks, id);
return rows > 0;
} catch (Exception e) {
System.err.println("Error updating manpower tender id " + id + ": " + e.getMessage());
return false;
}
}
public List<Map<String, Object>> getDesignationsForVendor(String vendorId) {
String sql = "SELECT " +
" des.id, " +
" des.designation, " +
" des.available_manpower, " +
" des.vacant_count, " +
" des.tender_manpower_designation_wise, " +
" COALESCE(des.File_no, '-') AS File_no, " +
" COALESCE(des.work_order_issue_date, '-') AS work_order_issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS work_order_end_date " +
"FROM employee_workorder_details des " +
"WHERE des.vid = ? AND des.isactive = 1 AND des.isdelete = 0 " +
"ORDER BY des.orderby, des.designation";
try {
return jdbcManpowerTemplate.queryForList(sql, vendorId);
} catch (Exception e) {
System.err.println("Error fetching designations for vendor ID " + vendorId + ": " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getVendorDetailsWithCounts() {
String sql = "SELECT id, vendor_name, description, total_employees, orderby, cdate, isactive, isdelete, " +
"(SELECT COUNT(*) FROM employee_details e WHERE (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) AND e.isactive = 1 AND e.isdelete = 0) AS actual_employee_count "
+
"FROM vendor_details v " +
"WHERE v.isactive = 1 AND v.isdelete = 0 " +
"ORDER BY v.orderby, v.vendor_name";
try {
List<Map<String, Object>> vendors = jdbcManpowerTemplate.queryForList(sql);
if (vendors != null) {
for (Map<String, Object> vendor : vendors) {
String vendorId = String.valueOf(vendor.get("id"));
List<Map<String, Object>> designations = getDesignationsForVendor(vendorId);
vendor.put("designationDetails", designations);
}
}
return vendors;
} catch (Exception e) {
System.err.println("Error fetching vendor details with counts: " + e.getMessage());
return new ArrayList<>();
}
}
public int getOverallTenderCount() {
String sql = "SELECT SUM(total_employees) FROM vendor_details WHERE isactive = 1 AND isdelete = 0";
try {
Integer sum = jdbcManpowerTemplate.queryForObject(sql, Integer.class);
return sum != null ? sum : 0;
} catch (Exception e) {
System.err.println("Error fetching overall tender count: " + e.getMessage());
return 0;
}
}
public int getOverallActiveEmployeeCount() {
String sql = "SELECT COUNT(*) FROM employee_details WHERE isactive = 1 AND isdelete = 0";
try {
Integer count = jdbcManpowerTemplate.queryForObject(sql, Integer.class);
return count != null ? count : 0;
} catch (Exception e) {
System.err.println("Error fetching overall active employee count: " + e.getMessage());
return 0;
}
}
public List<Map<String, Object>> getActiveLocations() {
String sql = "SELECT id, location_name FROM employee_location_details WHERE isactive = 1 AND isdelete = 0 " +
"ORDER BY " +
" CASE WHEN location_name LIKE 'zone_%' THEN 0 ELSE 1 END, " +
" CASE WHEN location_name LIKE 'zone_%' THEN CAST(SUBSTRING(location_name, 6) AS UNSIGNED) ELSE 0 END, "
+
" orderby, " +
" location_name";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching active locations: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getActiveDesignations() {
String sql = "SELECT id, designation, File_no FROM employee_workorder_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, designation";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching active designations: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getActiveUserTypes() {
String sql = "SELECT id, usertype FROM user_type_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, usertype";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching active user types: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getEmployeesByVendor(String vendorName) {
String vendorId = null;
try {
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList(
"SELECT id FROM vendor_details WHERE vendor_name = ? AND isactive = 1 AND isdelete = 0",
vendorName);
if (vendorList != null && !vendorList.isEmpty()) {
vendorId = String.valueOf(vendorList.get(0).get("id"));
}
} catch (Exception e) {
System.err.println("Error fetching vendor ID by name: " + e.getMessage());
}
String sql = "SELECT " +
" e.id, " +
" e.emp_code, " +
" e.name, " +
" e.email, " +
" e.mobile, " +
" e.vendor_id, " +
" e.isactive, " +
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " +
" COALESCE(ut.usertype, e.user_type) AS user_type, " +
" COALESCE(des.designation, e.designation) AS designation, " +
" COALESCE(loc.location_name, e.location) AS location, " +
" COALESCE(des.File_no, '-') AS work_order, " +
" COALESCE(des.work_order_issue_date, '-') AS issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS end_date " +
"FROM employee_details e " +
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " +
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) "
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"WHERE (e.vendor_id = ? OR e.vendor_id = ?) AND e.isactive = 1 AND e.isdelete = 0 " +
"ORDER BY e.name";
try {
return jdbcManpowerTemplate.queryForList(sql, vendorId != null ? vendorId : "", vendorName);
} catch (Exception e) {
System.err.println("Error fetching employees by vendor " + vendorName + ": " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getAllEmployees() {
String sql = "SELECT " +
" e.id, " +
" e.emp_code, " +
" e.name, " +
" e.email, " +
" e.mobile, " +
" e.vendor_id, " +
" e.isactive, " +
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " +
" COALESCE(ut.usertype, e.user_type) AS user_type, " +
" COALESCE(des.designation, e.designation) AS designation, " +
" COALESCE(loc.location_name, e.location) AS location, " +
" COALESCE(des.File_no, '-') AS work_order, " +
" COALESCE(des.work_order_issue_date, '-') AS issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS end_date " +
"FROM employee_details e " +
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " +
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) "
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"WHERE e.isactive = 1 AND e.isdelete = 0 " +
"ORDER BY e.name";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching all employees: " + e.getMessage());
return new ArrayList<>();
}
}
public Map<String, Object> getEmployeeByCode(String empCode) {
String sql = "SELECT id, emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, isactive, isdelete "
+
"FROM employee_details " +
"WHERE emp_code = ? AND isdelete = 0";
try {
List<Map<String, Object>> employees = jdbcManpowerTemplate.queryForList(sql, empCode);
if (employees != null && !employees.isEmpty()) {
return employees.get(0);
}
} catch (Exception e) {
System.err.println("Error fetching employee by code " + empCode + ": " + e.getMessage());
}
return null;
}
public boolean addEmployee(Map<String, Object> emp) {
String checkSql = "SELECT COUNT(*) FROM employee_details WHERE emp_code = ? AND isdelete = 0";
try {
Integer count = jdbcManpowerTemplate.queryForObject(checkSql, Integer.class, emp.get("emp_code"));
if (count != null && count > 0) {
return false;
}
} catch (Exception e) {
System.err.println("Error checking duplicate employee code: " + e.getMessage());
}
// Check designation limit
String designationIdOrName = String.valueOf(emp.get("designation"));
String vendorId = String.valueOf(emp.get("vendor_id"));
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details "
+
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0";
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId, designationIdOrName,
designationIdOrName);
if (rows != null && !rows.isEmpty()) {
for (Map<String, Object> row : rows) {
String desId = String.valueOf(row.get("id"));
String desName = (String) row.get("designation");
String tenderStr = (String) row.get("tender_manpower_designation_wise");
// Fetch the count of active, non-deleted employees for this designation/vendor
String countSql = "SELECT COUNT(*) FROM employee_details e " +
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) "
+
" AND (e.designation = ? OR e.designation = ?) " +
" AND e.isactive = 1 AND e.isdelete = 0";
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId,
desId, desName);
int currentActive = activeCount != null ? activeCount : 0;
int tenderLimit = 0;
if (tenderStr != null) {
try {
tenderLimit = Integer.parseInt(tenderStr.trim());
} catch (NumberFormatException ignored) {
}
}
if (currentActive >= tenderLimit) {
throw new RuntimeException("Manpower limit reached for designation '" + desName
+ "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive);
}
}
}
String sql = "INSERT INTO employee_details (emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, cdate, isactive, isdelete) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)";
try {
int updateRows = jdbcManpowerTemplate.update(sql,
emp.get("emp_code"),
emp.get("name"),
emp.get("email"),
emp.get("mobile"),
emp.get("user_type"),
emp.get("designation"),
emp.get("location"),
emp.get("vendor_id"),
emp.get("joined_date"));
if (updateRows > 0) {
recalculateDesignationManpower(String.valueOf(emp.get("designation")),
String.valueOf(emp.get("vendor_id")));
return true;
}
return false;
} catch (Exception e) {
System.err.println("Error adding employee: " + e.getMessage());
return false;
}
}
public boolean updateEmployee(Map<String, Object> emp) {
String empCode = (String) emp.get("emp_code");
String selectSql = "SELECT emp_code, name, email, mobile, user_type, designation, location, vendor_id, isactive, isdelete "
+
"FROM employee_details WHERE emp_code = ? AND isdelete = 0";
List<Map<String, Object>> oldList;
try {
oldList = jdbcManpowerTemplate.queryForList(selectSql, empCode);
} catch (Exception e) {
System.err.println("Error retrieving old employee details: " + e.getMessage());
return false;
}
if (oldList == null || oldList.isEmpty()) {
return false;
}
Map<String, Object> old = oldList.get(0);
// If designation or vendor_id is changing, check limit on the target
// designation/vendor
String oldDes = String.valueOf(old.get("designation"));
String oldVendor = String.valueOf(old.get("vendor_id"));
String newDes = String.valueOf(emp.get("designation"));
String newVendor = String.valueOf(emp.get("vendor_id"));
if (!oldDes.equals(newDes) || !oldVendor.equals(newVendor)) {
// Check designation limit for new designation & vendor
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details "
+
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0";
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, newVendor, newDes, newDes);
if (rows != null && !rows.isEmpty()) {
for (Map<String, Object> row : rows) {
String desId = String.valueOf(row.get("id"));
String desName = (String) row.get("designation");
String tenderStr = (String) row.get("tender_manpower_designation_wise");
String countSql = "SELECT COUNT(*) FROM employee_details e " +
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) "
+
" AND (e.designation = ? OR e.designation = ?) " +
" AND e.isactive = 1 AND e.isdelete = 0";
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, newVendor,
newVendor, desId, desName);
int currentActive = activeCount != null ? activeCount : 0;
int tenderLimit = 0;
if (tenderStr != null) {
try {
tenderLimit = Integer.parseInt(tenderStr.trim());
} catch (NumberFormatException ignored) {
}
}
if (currentActive >= tenderLimit) {
throw new RuntimeException("Manpower limit reached for designation '" + desName
+ "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive);
}
}
}
}
String historySql = "INSERT INTO employee_details_update_history (emp_code, name, email, mobile, user_type, designation, location, vendor_id, updated_date, isactive, isdelete) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?)";
try {
jdbcManpowerTemplate.update(historySql,
old.get("emp_code"),
old.get("name"),
old.get("email"),
old.get("mobile"),
old.get("user_type"),
old.get("designation"),
old.get("location"),
old.get("vendor_id"),
old.get("isactive"),
old.get("isdelete"));
} catch (Exception e) {
System.err.println("Error inserting history record: " + e.getMessage());
return false;
}
String updateSql = "UPDATE employee_details SET name = ?, email = ?, mobile = ?, user_type = ?, designation = ?, location = ?, vendor_id = ?, joined_date = ? "
+
"WHERE emp_code = ? AND isdelete = 0";
try {
int rows = jdbcManpowerTemplate.update(updateSql,
emp.get("name"),
emp.get("email"),
emp.get("mobile"),
emp.get("user_type"),
emp.get("designation"),
emp.get("location"),
emp.get("vendor_id"),
emp.get("joined_date"),
empCode);
if (rows > 0) {
recalculateDesignationManpower(String.valueOf(old.get("designation")),
String.valueOf(old.get("vendor_id")));
recalculateDesignationManpower(String.valueOf(emp.get("designation")),
String.valueOf(emp.get("vendor_id")));
return true;
}
return false;
} catch (Exception e) {
System.err.println("Error updating employee record: " + e.getMessage());
return false;
}
}
public List<Map<String, Object>> getEmployeeHistory(String empCode) {
String sql = "SELECT " +
" h.emp_code, " +
" h.name, " +
" h.email, " +
" h.mobile, " +
" h.updated_date, " +
" COALESCE(v.vendor_name, h.vendor_id) AS vendor_name, " +
" COALESCE(ut.usertype, h.user_type) AS user_type, " +
" COALESCE(des.designation, h.designation) AS designation, " +
" COALESCE(loc.location_name, h.location) AS location " +
"FROM employee_details_update_history h " +
"LEFT JOIN vendor_details v ON (h.vendor_id = CAST(v.id AS CHAR) OR h.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (h.user_type = CAST(ut.id AS CHAR) OR h.user_type = ut.usertype) " +
"LEFT JOIN employee_workorder_details des ON (h.designation = CAST(des.id AS CHAR) OR h.designation = des.designation) " +
"LEFT JOIN employee_location_details loc ON (h.location = CAST(loc.id AS CHAR) OR h.location = loc.location_name) " +
"WHERE h.emp_code = ? " +
"ORDER BY h.updated_date DESC";
try {
return jdbcManpowerTemplate.queryForList(sql, empCode);
} catch (Exception e) {
System.err.println("Error fetching employee history: " + e.getMessage());
return new ArrayList<>();
}
}
private boolean verifyPassword(String rawPassword, String dbPassword) {
if (dbPassword != null && dbPassword.startsWith("$2a$")) {
try {
// Dynamically load BCryptPasswordEncoder using reflection to prevent class
// loading errors
Class<?> clazz = Class.forName("org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder");
Object encoder = clazz.getDeclaredConstructor().newInstance();
java.lang.reflect.Method method = clazz.getMethod("matches", CharSequence.class, String.class);
return (Boolean) method.invoke(encoder, rawPassword, dbPassword);
} catch (ClassNotFoundException e) {
System.err.println(
"WARNING: BCryptPasswordEncoder class not found on active classpath! Falling back to plain text match.");
return rawPassword.equals(dbPassword);
} catch (Exception e) {
return rawPassword.equals(dbPassword);
}
}
return rawPassword != null && rawPassword.equals(dbPassword);
}
public long addVendor(String vendorName, String description, String totalEmployees, Integer orderby) {
if (orderby == null) {
try {
Integer maxOrder = jdbcManpowerTemplate.queryForObject("SELECT MAX(orderby) FROM vendor_details",
Integer.class);
orderby = (maxOrder != null) ? maxOrder + 1 : 1;
} catch (Exception e) {
orderby = 1;
}
}
final int finalOrderby = orderby;
KeyHolder keyHolder = new GeneratedKeyHolder();
try {
jdbcManpowerTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO vendor_details (vendor_name, description, total_employees, orderby, cdate, isactive, isdelete) "
+
"VALUES (?, ?, ?, ?, NOW(), 1, 0)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, vendorName);
ps.setString(2, description);
ps.setString(3, totalEmployees);
ps.setInt(4, finalOrderby);
return ps;
}, keyHolder);
Number key = keyHolder.getKey();
return key != null ? key.longValue() : 0;
} catch (Exception e) {
System.err.println("Error adding vendor to vendor_details: " + e.getMessage());
return 0;
}
}
public boolean addVendorWorkOrder(String vendorId, String description, String contractorName, String fileNo,
String subject, String issueDate, String endDate, String totalEmployees, Integer orderby) {
if (orderby == null) {
try {
Integer maxOrder = jdbcManpowerTemplate
.queryForObject("SELECT MAX(orderby) FROM employee_workorder_details", Integer.class);
orderby = (maxOrder != null) ? maxOrder + 1 : 1;
} catch (Exception e) {
orderby = 1;
}
}
String sql = "INSERT INTO employee_workorder_details (designation, vid, tender_manpower_designation_wise, available_manpower, vacant_count, contract_name, File_no, subject, work_order_issue_date, work_order_end_date, orderby, cdate, isactive, isdelete) "
+
"VALUES (?, ?, ?, '0', ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)";
try {
int rows = jdbcManpowerTemplate.update(sql,
subject, // designation
vendorId, // vid
totalEmployees, // tender_manpower_designation_wise
totalEmployees, // vacant_count
contractorName, // contract_name
fileNo, // File_no
subject, // subject
issueDate, // work_order_issue_date
endDate, // work_order_end_date
orderby // orderby
);
if (rows > 0) {
// Recalculate designation manpower (handles available_manpower and
// vacant_count)
recalculateDesignationManpower(subject, vendorId);
return true;
}
return false;
} catch (Exception e) {
System.err.println("Error adding work order to employee_workorder_details: " + e.getMessage());
return false;
}
}
public boolean updateVendorTotalEmployees(String vendorId, int addedEmployees) {
String selectSql = "SELECT total_employees FROM vendor_details WHERE id = ?";
try {
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(selectSql, vendorId);
if (rows == null || rows.isEmpty()) {
return false;
}
Object rawTotal = rows.get(0).get("total_employees");
String currentTotalStr = rawTotal != null ? String.valueOf(rawTotal) : "0";
int currentTotal = 0;
if (currentTotalStr != null) {
try {
currentTotal = Integer.parseInt(currentTotalStr.trim());
} catch (NumberFormatException ignored) {
}
}
int newTotal = currentTotal + addedEmployees;
String updateSql = "UPDATE vendor_details SET total_employees = ? WHERE id = ?";
int updateRows = jdbcManpowerTemplate.update(updateSql, String.valueOf(newTotal), vendorId);
return updateRows > 0;
} catch (Exception e) {
System.err.println("Error updating total employees for vendor ID " + vendorId + ": " + e.getMessage());
return false;
}
}
public void recalculateDesignationManpower(String designationIdOrName, String vendorId) {
if (designationIdOrName == null || designationIdOrName.trim().isEmpty() || vendorId == null
|| vendorId.trim().isEmpty()) {
return;
}
try {
// Resolve the designation row ID(s) from employee_workorder_details matching
// by ID or text designation name for the given vendor
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details "
+
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0";
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId,
designationIdOrName, designationIdOrName);
if (rows == null || rows.isEmpty()) {
return;
}
for (Map<String, Object> row : rows) {
String desId = String.valueOf(row.get("id"));
String desName = (String) row.get("designation");
String tenderStr = (String) row.get("tender_manpower_designation_wise");
// Count active employees for this vendor and designation
String countSql = "SELECT COUNT(*) FROM employee_details e " +
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) "
+
" AND (e.designation = ? OR e.designation = ?) " +
" AND e.isactive = 1 AND e.isdelete = 0";
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId,
desId, desName);
int available = activeCount != null ? activeCount : 0;
int tender = 0;
if (tenderStr != null) {
try {
tender = Integer.parseInt(tenderStr.trim());
} catch (NumberFormatException ignored) {
}
}
int vacant = tender - available;
String updateSql = "UPDATE employee_workorder_details SET available_manpower = ?, vacant_count = ? WHERE id = ?";
jdbcManpowerTemplate.update(updateSql, String.valueOf(available), String.valueOf(vacant), desId);
}
} catch (Exception e) {
System.err.println("Error recalculating designation manpower for designation: " + designationIdOrName
+ ", vendor: " + vendorId + ": " + e.getMessage());
}
}
public boolean deleteEmployee(String empCode) {
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ? AND isdelete = 0";
try {
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode);
if (list == null || list.isEmpty()) {
return false;
}
Map<String, Object> emp = list.get(0);
String designation = String.valueOf(emp.get("designation"));
String vendorId = String.valueOf(emp.get("vendor_id"));
String updateSql = "UPDATE employee_details SET isactive = 0, isdelete = 1 WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, empCode);
if (rows > 0) {
recalculateDesignationManpower(designation, vendorId);
return true;
}
} catch (Exception e) {
System.err.println("Error deleting employee: " + e.getMessage());
}
return false;
}
public boolean deleteVendor(String vendorId) {
try {
String selectVendorSql = "SELECT id, vendor_name FROM vendor_details WHERE id = ?";
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList(selectVendorSql, vendorId);
if (vendorList == null || vendorList.isEmpty()) {
return false;
}
String vendorName = (String) vendorList.get(0).get("vendor_name");
// 1. Soft-delete work orders in employee_workorder_details
String updateWorkOrdersSql = "UPDATE employee_workorder_details SET isactive = 0, isdelete = 1 WHERE vid = ?";
jdbcManpowerTemplate.update(updateWorkOrdersSql, vendorId);
// 2. Soft-delete employees under this vendor in employee_details
String updateEmployeesSql = "UPDATE employee_details SET isactive = 0, isdelete = 1 WHERE vendor_id = ? OR vendor_id = ?";
jdbcManpowerTemplate.update(updateEmployeesSql, vendorId, vendorName);
// 3. Soft-delete the vendor in vendor_details
String updateVendorSql = "UPDATE vendor_details SET isactive = 0, isdelete = 1 WHERE id = ?";
int rows = jdbcManpowerTemplate.update(updateVendorSql, vendorId);
return rows > 0;
} catch (Exception e) {
System.err.println("Error deleting vendor: " + e.getMessage());
return false;
}
}
public List<Map<String, Object>> getEmployeesByVendorAll(String vendorId) {
String vendorName = null;
try {
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList(
"SELECT vendor_name FROM vendor_details WHERE id = ?",
vendorId);
if (vendorList != null && !vendorList.isEmpty()) {
vendorName = (String) vendorList.get(0).get("vendor_name");
}
} catch (Exception e) {
System.err.println("Error fetching vendor name by ID: " + e.getMessage());
}
String sql = "SELECT " +
" e.id, " +
" e.emp_code, " +
" e.name, " +
" e.email, " +
" e.mobile, " +
" e.vendor_id, " +
" e.isactive, " +
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " +
" COALESCE(ut.usertype, e.user_type) AS user_type, " +
" COALESCE(des.designation, e.designation) AS designation, " +
" COALESCE(loc.location_name, e.location) AS location, " +
" COALESCE(des.File_no, '-') AS work_order " +
"FROM employee_details e " +
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " +
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) "
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"WHERE e.vendor_id = ? OR e.vendor_id = ? " +
"ORDER BY e.name";
try {
return jdbcManpowerTemplate.queryForList(sql, vendorId, vendorName != null ? vendorName : "");
} catch (Exception e) {
System.err.println("Error fetching all employees by vendor ID " + vendorId + ": " + e.getMessage());
return new ArrayList<>();
}
}
public boolean restoreEmployee(String empCode) {
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ?";
try {
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode);
if (list == null || list.isEmpty()) {
return false;
}
Map<String, Object> emp = list.get(0);
String designation = String.valueOf(emp.get("designation"));
String vendorId = String.valueOf(emp.get("vendor_id"));
String updateSql = "UPDATE employee_details SET isactive = 1, isdelete = 0 WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, empCode);
if (rows > 0) {
recalculateDesignationManpower(designation, vendorId);
return true;
}
} catch (Exception e) {
System.err.println("Error restoring employee: " + e.getMessage());
}
return false;
}
public List<Map<String, Object>> getAllEmployeesWithDeleted() {
String sql = "SELECT " +
" e.id, " +
" e.emp_code, " +
" e.name, " +
" e.email, " +
" e.mobile, " +
" e.vendor_id, " +
" e.isactive, " +
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " +
" COALESCE(ut.usertype, e.user_type) AS user_type, " +
" COALESCE(des.designation, e.designation) AS designation, " +
" COALESCE(loc.location_name, e.location) AS location, " +
" COALESCE(des.File_no, '-') AS work_order " +
"FROM employee_details e " +
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " +
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) "
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"ORDER BY e.name";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching all employees with deleted: " + e.getMessage());
return new ArrayList<>();
}
}
}

37
src/main/resources/application-local.properties

@ -0,0 +1,37 @@
spring.application.name=gcc-manpower
server.port=8084
#server.address=0.0.0.0
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# ===============================
# MySQL Database Configuration
# ===============================
# Local DB server
spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.password=1916008_Saec
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# spring.datasource.url=jdbc:oracle:thin:@10.1.0.14:1521:chncorp
# spring.datasource.username=fileadjust
# spring.datasource.password=fileadjust
# spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# ===============================
# JPA / Hibernate Config
# ===============================
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# spring.jpa.properties.hibernate.format_sql=true
spring.jpa.open-in-view=true
logging.level.org.hibernate.SQL=DEBUG
# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.hibernate.type.descriptor.sql=TRACE
# Production context path
server.servlet.context-path=/
# Disable Thymeleaf caching for hot-swapping templates
spring.thymeleaf.cache=false

44
src/main/resources/application-prod.properties

@ -0,0 +1,44 @@
spring.application.name=gcc-manpower
server.port=8084
#server.address=0.0.0.0
assets.base-url=/gcc-manpower/
fileBaseUrl=https://gccservices.in/gcc_manpower
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# ===============================
# MySQL Database Configuration
# ===============================
# Local DB server
# spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower
# spring.datasource.username=root
# spring.datasource.password=root
spring.datasource.url=jdbc:mysql://gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306/gcc_manpower
spring.datasource.username=root
spring.datasource.password=gcc-facial-password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# ===============================
# JPA / Hibernate Config
# ===============================
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# spring.jpa.properties.hibernate.format_sql=true
spring.jpa.open-in-view=true
logging.level.org.hibernate.SQL=DEBUG
# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.hibernate.type.descriptor.sql=TRACE
# Production context path
server.servlet.context-path=/gcc_manpower
# Disable Thymeleaf caching for hot-swapping templates
spring.thymeleaf.cache=false

5
src/main/resources/application.properties

@ -0,0 +1,5 @@
spring.profiles.active=local
#spring.profiles.active=prod
spring.application.name=gcc-manpower
server.servlet.session.timeout=10m

11794
src/main/resources/static/assets/bootstrap-5.3.2/css/bootstrap.min.css
File diff suppressed because it is too large
View File

2212
src/main/resources/static/assets/bootstrap-5.3.2/js/bootstrap.min.js
File diff suppressed because it is too large
View File

193
src/main/resources/static/assets/css/body-form-styles.css

@ -0,0 +1,193 @@
body {
margin: 0;
padding: 0;
min-height: 100vh; /* ensures gradient fills screen */
font-family: Arial, sans-serif;
background: linear-gradient(45deg, #f6f7fb, #4ca3b3);
background-repeat: no-repeat;
}
.form-title {
text-align: center;
margin-bottom: 20px;
font-size: 1.8rem;
color: #003366;
text-decoration: underline;
}
.header-row {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
.back-button {
background-color: white;
border: 2px solid black !important;
border-radius: 4px;
padding: 10px 20px;
border-radius: 5px;
font-size: 16px;
position: relative; /* override absolute */
color: #005f73;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.back-button:hover {
background: #4ca3b3;
transform: scale(1.05);
color: #000000;
}
.back-to-home-btn {
background-color: white;
border: 2px solid black !important;
border-radius: 4px;
padding: 10px 20px;
border-radius: 5px;
font-size: 16px;
position: relative; /* override absolute */
color: #005f73;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.back-to-home-btn:hover {
background: #4ca3b3;
transform: scale(1.05);
color: #000000;
}
.required {
color: red;
font-weight: bold;
}
.container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
h2 {
color: #0B3954;
margin-bottom: 30px;
}
.form-container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
.form-control {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.file-input {
display: flex;
gap: 10px;
}
.file-input button {
background: #f0f0f0;
border: none;
padding: 8px 15px;
border-radius: 4px;
}
.radio-group {
display: flex;
gap: 20px;
}
.radio-option {
display: flex;
align-items: center;
gap: 5px;
}
.submit-btn {
display: flex;
justify-content: center;
margin-top: 20px;
}
.submit-btn input[type="submit"]:disabled {
background-color: #cccccc; /* grey color */
color: #666666;
cursor: not-allowed;
}
.submit-btn input[type="submit"] {
background-color: #005f73;
color: #fff;
font-size: 1rem;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s ease;
}
.submit-btn input[type="submit"]:hover {
background: #005f05;
}
/* Responsive Design for Tablets */
@media ( max-width : 768px) {
.form-row {
grid-template-columns: 1fr;
}
.form-container {
padding: 20px;
}
.form-title {
font-size: 1.5rem;
}
.form-control {
font-size: 1rem;
}
}
/* Responsive Design for Mobile */
@media ( max-width : 480px) {
.form-row {
grid-template-columns: 1fr;
}
.submit-btn input[type="submit"] {
width: 100%;
font-size: 1.2rem;
padding: 12px;
}
.form-container {
padding: 15px;
}
.form-title {
font-size: 1.2rem;
}
}

114
src/main/resources/static/assets/css/datatable-styles.css

@ -0,0 +1,114 @@
#custom-info {
display: flex;
align-items: center;
}
#custom-search {
display: flex;
justify-content: flex-end; /* pushes it to the right */
align-items: center;
}
#custom-length {
display: flex;
align-items: center;
gap: 8px; /* Add spacing between dropdown and label */
white-space: nowrap; /* Prevent wrapping */
}
.dataTables_length label {
display: flex;
align-items: center;
margin: 0;
gap: 5px; /* Adjust spacing between text and dropdown */
}
.dataTables_length select {
display: inline-block;
width: auto; /* Ensure dropdown adjusts to content */
}
#custom-pagination {
display: flex;
justify-content: flex-end; /* Align pagination to the right */
align-items: center;
}
.dataTables_filter {
display: flex;
align-items: center;
gap: 8px; /* space between label and input */
margin: 0;
}
.dataTables_filter label {
margin: 0;
font-weight: 500;
display: flex;
align-items: center;
color: black;
}
.dataTables_filter input {
border: 1px solid #ccc;
border-radius: 5px;
padding: 5px 8px;
margin-left: 5px; /* small gap after label */
outline: none;
width: 180px;
transition: all 0.2s ease;
}
/* Apply border-radius to the table */
.det-table {
width: 100%; /* Table only grows as needed */
border-collapse: collapse;
table-layout: auto; /* Browser decides column width based on content */
border-spacing: 0;
border: 2px solid #f0f0f0;
margin-top: 20px;
font-family: Arial, sans-serif;
font-size: 14px;
border-radius: 12px;
overflow: hidden;
}
.det-table th, .det-table td {
border: 1px solid #e0e0e0;
padding: 6px 10px; /* Small padding keeps things compact */
white-space: nowrap; /* Prevents text wrapping → keeps columns narrow */
text-align: center;
}
.det-table td.no-word-wrap {
white-space: nowrap;
}
.det-table th {
background-color: #005f73;
color: white;
text-align: center;
}
.det-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.det-table tr:hover {
background-color: #f1f1f1;
}
.det-table th, .det-table td {
text-align: center;
}
@media (max-width: 767.98px) {
.btn-group {
width: 100%;
justify-content: center;
}
.btn-group .btn {
width: 48%; /* make buttons fill container nicely */
margin: 1px;
}
}

77
src/main/resources/static/assets/css/header-styles.css

@ -0,0 +1,77 @@
.header {
background-color: #005f73;
padding: 10px 20px;
display: flex;
align-items: center;
/* justify-content: space-between; */
position: relative;
color: white;
}
.header .logo-left img {
height: 60px;
width: 60px;
margin-right: 10px;
}
.header .title {
text-align: center;
/* flex-grow: 1; */
position: absolute;
transform: translateX(-50%);
left: 50%
}
.header .title h2 {
margin: 0;
font-size: 1.5rem;
}
.header .logos-right img {
height: 60px;
margin-left: 10px;
}
.header .logo-left {
display: flex;
align-items: center;
}
.header .logo-text {
display: flex;
flex-direction: column;
line-height: 1;
}
.header .logo-text h2 {
margin: 0;
font-size: 0.8rem;
color: white;
font-weight: bold;
}
.header .logo-text p {
margin: 0;
font-size: 0.5rem;
color: white;
}
@media ( max-width : 480px) {
.header {
flex-direction: column;
align-items: flex-start;
position: relative;
}
.header .logo-left img {
margin-bottom: 10px;
}
.header .logos-right img {
margin-bottom: 10px;
}
.header .title {
position: static; /* Reset absolute positioning */
transform: none; /* Remove the translate effect */
margin-top: 10px;
text-align: center; /* Ensure the text stays centered */
}
}

826
src/main/resources/static/assets/css/home-styles.css

@ -0,0 +1,826 @@
.button-row {
display: flex;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
/* allows wrapping */
margin-top: 20px;
text-align: center;
}
/* Center heading & align right-side search neatly */
.row.align-items-center.gx-3.justify-content-between {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.form-title {
margin: 0;
font-weight: 600;
text-align: center;
width: 100%;
}
/* Ensure dropdown + search align nicely */
.col-md-6.d-flex.justify-content-md-end {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 10px;
}
/* Center content properly in larger screens */
.form-container {
max-width: 1200px;
margin: 0 auto;
/* centers horizontally */
}
.form-control {
width: auto !important;
flex: 0 0 auto;
}
.custom-button {
padding: 15px 30px;
position: relative;
/* Important: makes absolute badge work */
background: white;
border: 1px solid #ccc;
border-radius: 10px;
font-size: 1rem;
color: #005f73;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
flex: 1 1 calc(33.333% - 40px);
/* 3 per row, minus gap space */
max-width: 300px;
/* optional: limit button width */
min-width: 200px;
/* optional: keep from shrinking too small */
}
.custom-button:hover {
background: #4ca3b3;
transform: scale(1.05);
color: #000000;
}
.custom-button:active {
transform: scale(0.95);
}
#vendor-filter,
#designation-filter {
min-width: 200px;
border-radius: 8px;
}
#search-input {
width: 250px;
max-width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
#search-input:focus {
outline: none;
border-color: #4ca3b3;
box-shadow: 0 3px 8px rgba(76, 163, 179, 0.3);
}
/* === Login Modal Overlay === */
.login-modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1050;
align-items: center;
justify-content: center;
}
/* === Modal Box === */
.login-modal-box {
background: #fff;
width: 400px;
max-width: 90%;
border-radius: 8px;
padding: 25px 30px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
position: relative;
animation: fadeIn 0.3s ease;
}
/* === Close Button === */
.close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 24px;
color: #666;
cursor: pointer;
}
.close-btn:hover {
color: #000;
}
/* === Title === */
.login-title {
text-align: center;
margin-bottom: 20px;
font-size: 22px;
color: #333;
font-weight: 600;
}
.login-btn-send {
background-color: #005f73;
color: #fff;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: background 0.2s ease;
}
.login-btn-send:hover {
background-color: #4ca3b3;
}
/* === Form === */
.login-form .form-label {
display: block;
font-size: 15px;
color: #444;
margin-bottom: 6px;
}
.req {
color: red;
}
.form-input {
width: 100%;
padding: 10px 12px;
font-size: 15px;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
}
.form-input:focus {
border-color: #00bcd4;
box-shadow: 0 0 4px rgba(0, 188, 212, 0.4);
}
/* === Button Row === */
.btn-row {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 12px;
}
/* === Animation states === */
.login-modal-overlay.show {
animation: fadeIn 0.3s ease forwards;
}
.login-modal-overlay.hide {
animation: fadeOut 0.3s ease forwards;
}
.url-num-badge {
position: absolute;
top: -12px;
left: -12px;
background-color: #005f73;
/* or any color */
color: white;
font-size: 14px;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
z-index: 10;
}
/* === Fade Animations === */
@keyframes fadeIn {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes fadeOut {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0.95);
opacity: 0;
}
}
/* Fix heading centering in large screens */
@media (min-width: 992px) {
.row.align-items-center.gy-3.justify-content-center.text-center {
justify-content: space-between !important;
}
.row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:first-child {
text-align: center !important;
flex: 1 1 100%;
max-width: 100%;
}
.row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:last-child {
flex: 1 1 auto;
max-width: fit-content;
margin-left: auto;
}
}
/* Tablet screens - shrink more */
@media (max-width : 768px) {
.form-title {
font-size: 1.2rem !important;
}
.button-row {
justify-content: center;
align-items: center;
flex-direction: column;
gap: 15px;
/* tighter spacing for small screens */
}
.custom-button {
flex: none;
width: 80%;
/* nicely centered buttons */
max-width: 320px;
min-width: unset;
/* remove constraint */
}
}
/* Responsive tweak for small screens */
@media (max-width: 576px) {
.form-title {
font-size: 1.1rem !important;
text-align: center;
}
#search-input {
width: 100%;
max-width: 280px;
}
#vendor-filter,
#designation-filter {
width: 100% !important;
max-width: 280px;
}
}
/* */
.vendor-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-top: 20px;
margin-bottom: 40px;
text-align: left;
}
.vendor-card {
background: #ffffff;
border: 1px solid #eef2f6;
border-radius: 12px;
padding: 22px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
}
.vendor-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background-color: #0f5c78;
opacity: 0.8;
}
.vendor-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(15, 92, 120, 0.08);
border-color: #d4e7ee;
}
.vendor-card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
}
.vendor-card-title {
font-size: 1.15rem;
font-weight: 800;
color: #2c3e50;
margin: 0;
text-transform: capitalize;
line-height: 1.3;
}
.vendor-card-desc {
font-size: 0.9rem;
color: #7f8c8d;
line-height: 1.4;
margin-bottom: 20px;
flex-grow: 1;
}
.vendor-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 15px;
border-top: 1px dashed #eef2f6;
}
.vendor-badge {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 12px;
background-color: #eef7fa;
color: #0f5c78;
}
/* Employee Count Interactive Badge */
.emp-count-badge {
background: linear-gradient(135deg, #0f5c78, #1e82a6);
color: #ffffff;
font-weight: 800;
font-size: 1.05rem;
padding: 8px 18px;
border-radius: 30px;
cursor: pointer;
border: none;
box-shadow: 0 4px 10px rgba(30, 130, 166, 0.25);
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.emp-count-badge:hover {
transform: scale(1.08);
box-shadow: 0 6px 15px rgba(30, 130, 166, 0.4);
background: linear-gradient(135deg, #1e82a6, #249cc8);
color: #ffffff;
}
.emp-count-badge:active {
transform: scale(0.96);
}
/* Modal styling overrides */
.modal-custom-header {
background-color: #0f5c78;
color: white;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-custom-title {
font-weight: 800;
font-size: 1.35rem;
display: flex;
align-items: center;
gap: 10px;
margin: 0;
}
.modal-search-box {
position: relative;
margin-bottom: 20px;
flex-grow: 1;
}
.modal-search-box input {
padding: 10px 15px 10px 40px;
border: 1px solid #dcdfe3;
border-radius: 8px;
width: 100%;
font-size: 0.95rem;
transition: all 0.3s ease;
}
.modal-search-box input:focus {
border-color: #0f5c78;
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15);
outline: none;
}
.modal-search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #7f8c8d;
}
.modal-table {
font-size: 0.9rem;
text-align: left;
}
.modal-table th {
background-color: #eef7fa !important;
color: #0f5c78 !important;
font-weight: 700;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
border-bottom: 2px solid #0f5c78 !important;
}
.modal-table td {
vertical-align: middle;
}
.modal-table-total-row {
font-weight: 800;
border-top: 2px solid #0f5c78 !important;
}
.modal-table-total-row td {
background-color: #eef7fa !important;
color: #2c3e50 !important;
}
.modal-body {
padding: 25px;
}
.export-btn {
background-color: #27ae60;
color: white;
border: none;
padding: 8px 16px;
font-size: 0.9rem;
font-weight: 700;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.export-btn:hover {
background-color: #219653;
color: white;
}
/* Summary Cards Section */
.summary-dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin: 25px 0 35px 0;
text-align: left;
}
.summary-card {
background: #ffffff;
border: 1px solid #eef2f6;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.01);
display: flex;
align-items: center;
gap: 15px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.summary-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.05);
border-color: #d4e7ee;
}
.summary-card.interactive {
cursor: pointer;
}
.summary-card.interactive:hover {
border-color: #27ae60;
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.12);
}
.summary-icon-container {
width: 50px;
height: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: #ffffff;
}
.summary-details {
display: flex;
flex-direction: column;
}
.summary-title {
font-size: 0.8rem;
font-weight: 700;
color: #7f8c8d;
margin: 0 0 4px 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.summary-value {
font-size: 1.6rem;
font-weight: 800;
color: #2c3e50;
margin: 0;
line-height: 1;
}
/* Custom Colors for Icons */
.bg-summary-vendors {
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
}
.bg-summary-tender {
background: linear-gradient(135deg, #0f5c78, #1e82a6);
}
.bg-summary-existing {
background: linear-gradient(135deg, #27ae60, #2ecc71);
}
.bg-summary-vacant {
background: linear-gradient(135deg, #d35400, #e67e22);
}
/* Vendor Card Designation List Styles */
.vendor-desig-section {
margin-top: 15px;
margin-bottom: 18px;
text-align: left;
}
.vendor-desig-title {
font-size: 0.75rem;
font-weight: 800;
color: #0f5c78;
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.vendor-desig-container {
background-color: #f8fafc;
border: 1px solid #f1f5f9;
border-radius: 8px;
padding: 10px 14px;
max-height: 140px;
overflow-y: auto;
}
.vendor-desig-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #f1f5f9;
font-size: 0.8rem;
}
.vendor-desig-row:last-child {
border-bottom: none;
}
.vendor-desig-row.clickable {
cursor: pointer;
transition: background-color 0.2s ease, padding-left 0.2s ease, padding-right 0.2s ease;
border-radius: 4px;
padding: 6px 8px;
margin: 1px -8px;
}
.vendor-desig-row.clickable:hover {
background-color: #eef7fa;
padding-left: 12px;
}
.vendor-desig-name {
color: #334155;
font-weight: 700;
}
.vendor-desig-counts {
display: flex;
align-items: center;
gap: 6px;
}
.vendor-desig-vacant-badge {
background-color: #fee2e2;
color: #ef4444;
font-weight: 700;
font-size: 0.72rem;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #fca5a5;
}
.vendor-desig-tender-label {
color: #64748b;
font-size: 0.7rem;
font-weight: 500;
}
/* Premium Date Filter Styling */
.date-filter-container {
display: inline-flex;
align-items: flex-end;
justify-content: center;
gap: 15px;
background: #ffffff;
padding: 15px 25px;
border-radius: 12px;
border: 1px solid #e2e8f0;
box-shadow: 0 4px 12px rgba(15, 92, 120, 0.05);
margin: 15px auto 20px auto;
max-width: 100%;
flex-wrap: wrap;
transition: all 0.3s ease;
}
.date-filter-container:hover {
box-shadow: 0 6px 18px rgba(15, 92, 120, 0.08);
border-color: #cbd5e1;
}
.date-input-group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.date-input-group label {
font-size: 0.75rem;
font-weight: 800;
color: #0f5c78;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0;
display: flex;
align-items: center;
gap: 6px;
}
.date-picker-input {
padding: 8px 36px 8px 12px;
border: 1.5px solid #cbd5e1;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
color: #334155;
background-color: #f8fafc;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%237f8c8d'%3E%3Cpath d='M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px 16px;
transition: all 0.3s ease;
outline: none;
height: 38px;
width: 175px;
cursor: pointer;
}
.date-picker-input:focus,
.date-picker-input:hover {
border-color: #0f5c78;
background-color: #ffffff;
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.12);
}
.date-filter-separator {
display: flex;
align-items: center;
justify-content: center;
height: 38px;
color: #94a3b8;
font-size: 0.9rem;
}
.date-filter-btn {
height: 38px;
padding: 0 20px;
background: linear-gradient(135deg, #0f5c78, #1e82a6);
color: #ffffff;
font-weight: 700;
font-size: 0.9rem;
border: none;
border-radius: 8px;
cursor: pointer;
box-shadow: 0 4px 8px rgba(30, 130, 166, 0.2);
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.date-filter-btn:hover {
background: linear-gradient(135deg, #1e82a6, #249cc8);
box-shadow: 0 5px 12px rgba(30, 130, 166, 0.3);
transform: translateY(-1px);
}
.date-filter-btn:active {
transform: translateY(1px);
}
/* Responsive styling for date filter */
@media (max-width: 576px) {
.date-filter-container {
flex-direction: column;
align-items: stretch;
width: 100%;
padding: 15px;
gap: 12px;
}
.date-input-group {
width: 100%;
}
.date-picker-input {
width: 100%;
}
.date-filter-separator {
display: none;
}
.date-filter-btn {
justify-content: center;
margin-top: 5px;
}
}

272
src/main/resources/static/assets/css/login-form-styles.css

@ -0,0 +1,272 @@
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 100;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2") format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 200;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-200.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 300;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-300.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-regular.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 500;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-500.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 600;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-600.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 700;
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 100;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-100.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 200;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-200.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 300;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-300.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-regular.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 500;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-500.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 600;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-600.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 700;
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-700.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 100;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-100.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 200;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-200.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 300;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-300.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-regular.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 500;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-500.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 600;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-600.woff2")
format("woff2");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 700;
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-700.woff2")
format("woff2");
}
* {
margin: 0;
border: 0;
padding: 0;
box-sizing: border-box;
font-family: "Poppins", sans-serif;
}
body {
background: linear-gradient(45deg, #f6f7fb, #4ca3b3);
background-repeat: no-repeat;
min-height: 100vh;
min-width: 100vw;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
margin: 0;
padding: 0;
}
div.container {
background: white;
min-width: 320px;
min-height: 33vh;
padding: 2rem;
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.2);
border-radius: 8px;
}
div h2 {
font-weight: 600;
margin-bottom: 2rem;
position: relative;
}
.container h3,
.container h4 {
text-align: center;
}
div h2::before {
content: "";
position: absolute;
height: 4px;
width: 25px;
bottom: 3px;
left: 0;
border-radius: 8px;
background: #005f73;
}
form {
display: flex;
flex-direction: column;
}
.input-field {
position: relative;
margin-bottom: 1.5rem; /* instead of targeting only first-child */
}
.input-field .underline::before {
content: "";
position: absolute;
height: 1px;
width: 100%;
bottom: -5px;
left: 0;
background: rgba(0, 0, 0, 0.2);
}
.input-field .underline::after {
content: "";
position: absolute;
height: 1px;
width: 100%;
bottom: -5px;
left: 0;
background: linear-gradient(45deg, #8e2de2, #4a00e0);
transform: scaleX(0);
transition: all 0.3s ease-in-out;
transform-origin: left;
}
.input-field input:focus ~ .underline::after {
transform: scaleX(1);
}
.input-field input {
outline: none;
font-size: 0.9rem;
color: rgba(0, 0, 0, 0.7);
width: 100%;
}
.input-field input::placeholder {
color: rgba(0, 0, 0, 0.5);
}
form input[type="submit"] {
margin-top: 2rem;
padding: 0.4rem;
width: 100%;
background: #005f73;
cursor: pointer;
color: white;
font-size: 0.9rem;
font-weight: 300;
border-radius: 4px;
transition: all 0.3s ease;
}
form input[type="submit"]:hover {
letter-spacing: 0.5px;
background: #005f05;
}
.small-logo {
width: 60px;
height: 60px;
display: block;
margin: 0 auto; /* Centers the image */
}

532
src/main/resources/static/assets/datatables-1.13.6/css/dataTables.bootstrap5.min.css

@ -0,0 +1,532 @@
:root {
--dt-row-selected: 13, 110, 253;
--dt-row-selected-text: 255, 255, 255;
--dt-row-selected-link: 9, 10, 11;
--dt-row-stripe: 0, 0, 0;
--dt-row-hover: 0, 0, 0;
--dt-column-ordering: 0, 0, 0;
--dt-html-background: white;
}
:root.dark {
--dt-html-background: rgb(33, 37, 41);
}
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
display: inline-block;
color: rgba(0, 0, 0, 0.5);
content: "►";
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "▼";
}
html.dark table.dataTable td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
}
html.dark table.dataTable tr.dt-hasChild td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
}
table.dataTable thead > tr > th.sorting,
table.dataTable thead > tr > th.sorting_asc,
table.dataTable thead > tr > th.sorting_desc,
table.dataTable thead > tr > th.sorting_asc_disabled,
table.dataTable thead > tr > th.sorting_desc_disabled,
table.dataTable thead > tr > td.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting_asc_disabled,
table.dataTable thead > tr > td.sorting_desc_disabled {
cursor: pointer;
position: relative;
padding-right: 26px;
}
table.dataTable thead > tr > th.sorting:before,
table.dataTable thead > tr > th.sorting:after,
table.dataTable thead > tr > th.sorting_asc:before,
table.dataTable thead > tr > th.sorting_asc:after,
table.dataTable thead > tr > th.sorting_desc:before,
table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > th.sorting_asc_disabled:after,
table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
position: absolute;
display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.8em;
}
table.dataTable thead > tr > th.sorting:before,
table.dataTable thead > tr > th.sorting_asc:before,
table.dataTable thead > tr > th.sorting_desc:before,
table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "▲";
content: "▲"/"";
}
table.dataTable thead > tr > th.sorting:after,
table.dataTable thead > tr > th.sorting_asc:after,
table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > th.sorting_asc_disabled:after,
table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%;
content: "▼";
content: "▼"/"";
}
table.dataTable thead > tr > th.sorting_asc:before,
table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:after {
opacity: 0.6;
}
table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before {
display: none;
}
table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before,
div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
display: none;
}
div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 2px;
}
div.dataTables_processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
div.dataTables_processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
height: 13px;
border-radius: 50%;
background: rgb(13, 110, 253);
background: rgb(var(--dt-row-selected));
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
div.dataTables_processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@keyframes datatables-loader-1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes datatables-loader-3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes datatables-loader-2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
table.dataTable tfoot td {
text-align: left;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
} /*! Bootstrap 5 integration for DataTables
*
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
table.dataTable {
clear: both;
margin-top: 6px !important;
margin-bottom: 6px !important;
max-width: none !important;
border-collapse: separate !important;
border-spacing: 0;
}
table.dataTable td,
table.dataTable th {
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n + 1) > * {
box-shadow: none;
}
table.dataTable > tbody > tr {
background-color: transparent;
}
table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
color: rgb(255, 255, 255);
color: rgb(var(--dt-row-selected-text));
}
table.dataTable > tbody > tr.selected a {
color: rgb(9, 10, 11);
color: rgb(var(--dt-row-selected-link));
}
table.dataTable.table-striped > tbody > tr.odd > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
}
table.dataTable.table-striped > tbody > tr.odd.selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
}
table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
}
div.dataTables_wrapper div.dataTables_length label {
font-weight: normal;
text-align: left;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_length select {
width: auto;
display: inline-block;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: right;
}
div.dataTables_wrapper div.dataTables_filter label {
font-weight: normal;
white-space: nowrap;
text-align: left;
}
div.dataTables_wrapper div.dataTables_filter input {
margin-left: 0.5em;
display: inline-block;
width: auto;
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 0.85em;
}
div.dataTables_wrapper div.dataTables_paginate {
margin: 0;
white-space: nowrap;
text-align: right;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin: 2px 0;
white-space: nowrap;
justify-content: flex-end;
}
div.dataTables_wrapper div.dt-row {
position: relative;
}
div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table > thead .sorting:before,
div.dataTables_scrollBody > table > thead .sorting_asc:before,
div.dataTables_scrollBody > table > thead .sorting_desc:before,
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
}
div.dataTables_scrollBody > table > tbody tr:first-child th,
div.dataTables_scrollBody > table > tbody tr:first-child td {
border-top: none;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
box-sizing: content-box;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
margin-top: 0 !important;
border-top: none;
}
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
text-align: center;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
justify-content: center !important;
}
}
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px;
}
table.table-bordered.dataTable {
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
}
div.dataTables_scrollHead table.table-bordered {
border-bottom-width: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive
> div.dataTables_wrapper
> div.row
> div[class^="col-"]:first-child {
padding-left: 0;
}
div.table-responsive
> div.dataTables_wrapper
> div.row
> div[class^="col-"]:last-child {
padding-right: 0;
}
:root[data-bs-theme="dark"] {
--dt-row-hover: 255, 255, 255;
--dt-row-stripe: 255, 255, 255;
--dt-column-ordering: 255, 255, 255;
}

80
src/main/resources/static/assets/datatables-1.13.6/js/dataTables.bootstrap5.min.js

@ -0,0 +1,80 @@
/*! DataTables Bootstrap 5 integration
* 2020 SpryMedia Ltd - datatables.net/license
*/
! function(t) {
var n, r;
"function" == typeof define && define.amd ? define(["jquery", "datatables.net"], function(e) {
return t(e, window, document)
}) : "object" == typeof exports ? (n = require("jquery"), r = function(e, a) {
a.fn.dataTable || require("datatables.net")(e, a)
}, "undefined" == typeof window ? module.exports = function(e, a) {
return e = e || window, a = a || n(e), r(e, a), t(a, 0, e.document)
} : (r(window, n), module.exports = t(n, window, window.document))) : t(jQuery, window, document)
}(function(x, e, r, o) {
"use strict";
var i = x.fn.dataTable;
return x.extend(!0, i.defaults, {
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row dt-row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
renderer: "bootstrap"
}), x.extend(i.ext.classes, {
sWrapper: "dataTables_wrapper dt-bootstrap5",
sFilterInput: "form-control form-control-sm",
sLengthSelect: "form-select form-select-sm",
sProcessing: "dataTables_processing card",
sPageButton: "paginate_button page-item"
}), i.ext.renderer.pageButton.bootstrap = function(d, e, s, a, l, c) {
function u(e, a) {
for (var t, n, r = function(e) {
e.preventDefault(), x(e.currentTarget).hasClass("disabled") || b.page() == e.data.action || b.page(e.data.action).draw("page")
}, o = 0, i = a.length; o < i; o++)
if (t = a[o], Array.isArray(t)) u(e, t);
else {
switch (f = p = "", t) {
case "ellipsis":
p = "&#x2026;", f = "disabled";
break;
case "first":
p = g.sFirst, f = t + (0 < l ? "" : " disabled");
break;
case "previous":
p = g.sPrevious, f = t + (0 < l ? "" : " disabled");
break;
case "next":
p = g.sNext, f = t + (l < c - 1 ? "" : " disabled");
break;
case "last":
p = g.sLast, f = t + (l < c - 1 ? "" : " disabled");
break;
default:
p = t + 1, f = l === t ? "active" : ""
}
p && (n = -1 !== f.indexOf("disabled"), n = x("<li>", {
class: m.sPageButton + " " + f,
id: 0 === s && "string" == typeof t ? d.sTableId + "_" + t : null
}).append(x("<a>", {
href: n ? null : "#",
"aria-controls": d.sTableId,
"aria-disabled": n ? "true" : null,
"aria-label": w[t],
role: "link",
"aria-current": "active" === f ? "page" : null,
"data-dt-idx": t,
tabindex: n ? -1 : d.iTabIndex,
class: "page-link"
}).html(p)).appendTo(e), d.oApi._fnBindAction(n, {
action: t
}, r))
}
}
var p, f, t, b = new i.Api(d),
m = d.oClasses,
g = d.oLanguage.oPaginate,
w = d.oLanguage.oAria.paginate || {},
e = x(e);
try {
t = e.find(r.activeElement).data("dt-idx")
} catch (e) { }
var n = e.children("ul.pagination");
n.length ? n.empty() : n = e.html("<ul/>").children("ul").addClass("pagination"), u(n, a), t !== o && e.find("[data-dt-idx=" + t + "]").trigger("focus")
}, i
});

2990
src/main/resources/static/assets/datatables-1.13.6/js/jquery.dataTables.min.js
File diff suppressed because it is too large
View File

248
src/main/resources/static/assets/datatables-buttons-2.4.1/css/buttons.bootstrap5.min.css

@ -0,0 +1,248 @@
@keyframes dtb-spinner {
100% {
transform: rotate(360deg);
}
}
@-o-keyframes dtb-spinner {
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-ms-keyframes dtb-spinner {
100% {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes dtb-spinner {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes dtb-spinner {
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
div.dataTables_wrapper {
position: relative;
}
div.dt-buttons {
position: initial;
}
div.dt-buttons .dt-button {
overflow: hidden;
text-overflow: ellipsis;
}
div.dt-button-info {
position: fixed;
top: 50%;
left: 50%;
width: 400px;
margin-top: -100px;
margin-left: -200px;
background-color: white;
border-radius: 0.75em;
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8);
text-align: center;
z-index: 2003;
overflow: hidden;
}
div.dt-button-info h2 {
padding: 2rem 2rem 1rem 2rem;
margin: 0;
font-weight: normal;
}
div.dt-button-info > div {
padding: 1em 2em 2em 2em;
}
div.dtb-popover-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
text-align: center;
border-radius: 3px;
cursor: pointer;
z-index: 2003;
}
button.dtb-hide-drop {
display: none !important;
}
div.dt-button-collection-title {
text-align: center;
padding: 0.3em 0 0.5em;
margin-left: 0.5em;
margin-right: 0.5em;
font-size: 0.9em;
}
div.dt-button-collection-title:empty {
display: none;
}
span.dt-button-spacer {
display: inline-block;
margin: 0.5em;
white-space: nowrap;
}
span.dt-button-spacer.bar {
border-left: 1px solid rgba(0, 0, 0, 0.3);
vertical-align: middle;
padding-left: 0.5em;
}
span.dt-button-spacer.bar:empty {
height: 1em;
width: 1px;
padding-left: 0;
}
div.dt-button-collection .dt-button-active {
padding-right: 3em;
}
div.dt-button-collection .dt-button-active:after {
position: absolute;
top: 50%;
margin-top: -10px;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection .dt-button-active.dt-button-split {
padding-right: 0;
}
div.dt-button-collection .dt-button-active.dt-button-split:after {
display: none;
}
div.dt-button-collection .dt-button-active.dt-button-split > *:first-child {
padding-right: 3em;
}
div.dt-button-collection
.dt-button-active.dt-button-split
> *:first-child:after {
position: absolute;
top: 50%;
margin-top: -10px;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection .dt-button-active-a a {
padding-right: 3em;
}
div.dt-button-collection .dt-button-active-a a:after {
position: absolute;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection span.dt-button-spacer {
width: 100%;
font-size: 0.9em;
text-align: center;
margin: 0.5em 0;
}
div.dt-button-collection span.dt-button-spacer:empty {
height: 0;
width: 100%;
}
div.dt-button-collection span.dt-button-spacer.bar {
border-left: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding-left: 0;
}
html.dark div.dt-button-info {
background-color: var(--dt-html-background);
border: 1px solid rgba(255, 255, 255, 0.15);
}
div.dt-buttons div.btn-group {
position: initial;
}
div.dt-buttons div.dropdown-menu {
margin-top: 4px;
}
div.dt-buttons div.dropdown-menu .dt-button {
position: relative;
}
div.dt-buttons div.dropdown-menu div.dt-button-split {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
align-items: stretch;
}
div.dt-buttons div.dropdown-menu div.dt-button-split a:first-child {
min-width: auto;
flex: 1 0 50px;
padding-right: 0;
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child {
min-width: 33px;
flex: 0;
background: transparent;
border: none;
line-height: 1rem;
color: var(--bs-dropdown-link-color);
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:hover {
color: var(--bs-dropdown-link-hover-color);
background-color: var(--bs-dropdown-link-hover-bg);
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:after {
position: relative;
left: -3px;
}
div.dt-buttons span.dt-button-spacer.empty {
margin: 1px;
}
div.dt-buttons span.dt-button-spacer.bar:empty {
height: inherit;
}
div.dt-buttons .btn.processing {
color: rgba(0, 0, 0, 0.2);
}
div.dt-buttons .btn.processing:after {
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
box-sizing: border-box;
display: block;
content: " ";
border: 2px solid rgb(40, 40, 40);
border-radius: 50%;
border-left-color: transparent;
border-right-color: transparent;
animation: dtb-spinner 1500ms infinite linear;
-o-animation: dtb-spinner 1500ms infinite linear;
-ms-animation: dtb-spinner 1500ms infinite linear;
-webkit-animation: dtb-spinner 1500ms infinite linear;
-moz-animation: dtb-spinner 1500ms infinite linear;
}
div.dt-button-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
@media screen and (max-width: 767px) {
div.dt-buttons {
float: none;
width: 100%;
text-align: center;
margin-bottom: 0.5em;
}
div.dt-buttons a.btn {
float: none;
}
}

70
src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.bootstrap5.min.js

@ -0,0 +1,70 @@
/*! Bootstrap integration for DataTables' Buttons
* © SpryMedia Ltd - datatables.net/license
*/
! function(o) {
var e, a;
"function" == typeof define && define.amd ? define(["jquery", "datatables.net-bs5", "datatables.net-buttons"], function(t) {
return o(t, window, document)
}) : "object" == typeof exports ? (e = require("jquery"), a = function(t, n) {
n.fn.dataTable || require("datatables.net-bs5")(t, n), n.fn.dataTable.Buttons || require("datatables.net-buttons")(t, n)
}, "undefined" == typeof window ? module.exports = function(t, n) {
return t = t || window, n = n || e(t), a(t, n), o(n, 0, t.document)
} : (a(window, e), module.exports = o(e, window, window.document))) : o(jQuery, window, document)
}(function(o, t, n, e) {
"use strict";
var a = o.fn.dataTable;
return o.extend(!0, a.Buttons.defaults, {
dom: {
container: {
className: "dt-buttons btn-group flex-wrap"
},
button: {
className: "btn btn-secondary",
active: "active"
},
collection: {
action: {
dropHtml: ""
},
container: {
tag: "div",
className: "dropdown-menu dt-button-collection"
},
closeButton: !1,
button: {
tag: "a",
className: "dt-button dropdown-item",
active: "dt-button-active",
disabled: "disabled",
spacer: {
className: "dropdown-divider",
tag: "hr"
}
}
},
split: {
action: {
tag: "a",
className: "btn btn-secondary dt-button-split-drop-button",
closeButton: !1
},
dropdown: {
tag: "button",
dropHtml: "",
className: "btn btn-secondary dt-button-split-drop dropdown-toggle dropdown-toggle-split",
closeButton: !1,
align: "split-left",
splitAlignClass: "dt-button-split-left"
},
wrapper: {
tag: "div",
className: "dt-button-split btn-group",
closeButton: !1
}
}
},
buttonCreated: function(t, n) {
return t.buttons ? o('<div class="btn-group"/>').append(n) : n
}
}), a.ext.buttons.collection.className += " dropdown-toggle", a.ext.buttons.collection.rightAlignClassName = "dropdown-menu-right", a
});

538
src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js
File diff suppressed because it is too large
View File

79
src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.print.min.js

@ -0,0 +1,79 @@
/*!
* Print button for Buttons and DataTables.
* © SpryMedia Ltd - datatables.net/license
*/
! function(n) {
var o, r;
"function" == typeof define && define.amd ? define(["jquery", "datatables.net", "datatables.net-buttons"], function(t) {
return n(t, window, document)
}) : "object" == typeof exports ? (o = require("jquery"), r = function(t, e) {
e.fn.dataTable || require("datatables.net")(t, e), e.fn.dataTable.Buttons || require("datatables.net-buttons")(t, e)
}, "undefined" == typeof window ? module.exports = function(t, e) {
return t = t || window, e = e || o(t), r(t, e), n(e, t, t.document)
} : (r(window, o), module.exports = n(o, window, window.document))) : n(jQuery, window, document)
}(function(m, b, t, p) {
"use strict";
function h(t) {
return n.href = t, -1 === (t = n.host).indexOf("/") && 0 !== n.pathname.indexOf("/") && (t += "/"), n.protocol + "//" + t + n.pathname + n.search
}
var e = m.fn.dataTable,
n = t.createElement("a");
return e.ext.buttons.print = {
className: "buttons-print",
text: function(t) {
return t.i18n("buttons.print", "Print")
},
action: function(t, e, n, o) {
function r(t, e) {
for (var n = "<tr>", o = 0, r = t.length; o < r; o++) {
var i = null === t[o] || t[o] === p ? "" : t[o];
n += "<" + e + " " + (s[o] ? 'class="' + s[o] + '"' : "") + ">" + i + "</" + e + ">"
}
return n + "</tr>"
}
var i = e.buttons.exportData(m.extend({
decodeEntities: !1
}, o.exportOptions)),
a = e.buttons.exportInfo(o),
s = e.columns(o.exportOptions.columns).flatten().map(function(t) {
return e.settings()[0].aoColumns[e.column(t).index()].sClass
}).toArray(),
u = '<table class="' + e.table().node().className + '">';
o.header && (u += "<thead>" + r(i.header, "th") + "</thead>"), u += "<tbody>";
for (var d = 0, c = i.body.length; d < c; d++) u += r(i.body[d], "td");
u += "</tbody>", o.footer && i.footer && (u += "<tfoot>" + r(i.footer, "th") + "</tfoot>"), u += "</table>";
var l = b.open("", "");
if (l) {
l.document.close();
var f = "<title>" + a.title + "</title>";
m("style, link").each(function() {
f += function(t) {
t = m(t).clone()[0];
return "link" === t.nodeName.toLowerCase() && (t.href = h(t.href)), t.outerHTML
}(this)
});
try {
l.document.head.innerHTML = f
} catch (t) {
m(l.document.head).html(f)
}
l.document.body.innerHTML = "<h1>" + a.title + "</h1><div>" + (a.messageTop || "") + "</div>" + u + "<div>" + (a.messageBottom || "") + "</div>", m(l.document.body).addClass("dt-print-view"), m("img", l.document.body).each(function(t, e) {
e.setAttribute("src", h(e.getAttribute("src")))
}), o.customize && o.customize(l, o, e);
a = function() {
o.autoPrint && (l.print(), l.close())
};
navigator.userAgent.match(/Trident\/\d.\d/) ? a() : l.setTimeout(a, 1e3)
} else e.buttons.info(e.i18n("buttons.printErrorTitle", "Unable to open print view"), e.i18n("buttons.printErrorMsg", "Please allow popups in your browser for this site to be able to view the print view."), 5e3)
},
title: "*",
messageTop: "*",
messageBottom: "*",
exportOptions: {},
header: !0,
footer: !1,
autoPrint: !0,
customize: null
}, e
});

764
src/main/resources/static/assets/datatables-buttons-2.4.1/js/dataTables.buttons.min.js

@ -0,0 +1,764 @@
/*! Buttons for DataTables 2.4.1
* © SpryMedia Ltd - datatables.net/license
*/
! function(e) {
var o, i;
"function" == typeof define && define.amd ? define(["jquery", "datatables.net"], function(t) {
return e(t, window, document)
}) : "object" == typeof exports ? (o = require("jquery"), i = function(t, n) {
n.fn.dataTable || require("datatables.net")(t, n)
}, "undefined" == typeof window ? module.exports = function(t, n) {
return t = t || window, n = n || o(t), i(t, n), e(n, t, t.document)
} : (i(window, o), module.exports = e(o, window, window.document))) : e(jQuery, window, document)
}(function(y, v, x, w) {
"use strict";
var e = y.fn.dataTable,
o = 0,
C = 0,
_ = e.ext.buttons;
function A(t, n, e) {
y.fn.animate ? t.stop().fadeIn(n, e) : (t.css("display", "block"), e && e.call(t))
}
function k(t, n, e) {
y.fn.animate ? t.stop().fadeOut(n, e) : (t.css("display", "none"), e && e.call(t))
}
function B(n, t) {
if (!(this instanceof B)) return function(t) {
return new B(t, n).container()
};
!0 === (t = void 0 === t ? {} : t) && (t = {}), Array.isArray(t) && (t = {
buttons: t
}), this.c = y.extend(!0, {}, B.defaults, t), t.buttons && (this.c.buttons = t.buttons), this.s = {
dt: new e.Api(n),
buttons: [],
listenKeys: "",
namespace: "dtb" + o++
}, this.dom = {
container: y("<" + this.c.dom.container.tag + "/>").addClass(this.c.dom.container.className)
}, this._constructor()
}
y.extend(B.prototype, {
action: function(t, n) {
t = this._nodeToButton(t);
return n === w ? t.conf.action : (t.conf.action = n, this)
},
active: function(t, n) {
var t = this._nodeToButton(t),
e = this.c.dom.button.active,
o = y(t.node);
return t.inCollection && this.c.dom.collection.button && this.c.dom.collection.button.active !== w && (e = this.c.dom.collection.button.active), n === w ? o.hasClass(e) : (o.toggleClass(e, n === w || n), this)
},
add: function(t, n, e) {
var o = this.s.buttons;
if ("string" == typeof n) {
for (var i = n.split("-"), s = this.s, a = 0, r = i.length - 1; a < r; a++) s = s.buttons[+i[a]];
o = s.buttons, n = +i[i.length - 1]
}
return this._expandButton(o, t, t !== w ? t.split : w, (t === w || t.split === w || 0 === t.split.length) && s !== w, !1, n), e !== w && !0 !== e || this._draw(), this
},
collectionRebuild: function(t, n) {
var e = this._nodeToButton(t);
if (n !== w) {
for (var o = e.buttons.length - 1; 0 <= o; o--) this.remove(e.buttons[o].node);
for (e.conf.prefixButtons && n.unshift.apply(n, e.conf.prefixButtons), e.conf.postfixButtons && n.push.apply(n, e.conf.postfixButtons), o = 0; o < n.length; o++) {
var i = n[o];
this._expandButton(e.buttons, i, i !== w && i.config !== w && i.config.split !== w, !0, i.parentConf !== w && i.parentConf.split !== w, null, i.parentConf)
}
}
this._draw(e.collection, e.buttons)
},
container: function() {
return this.dom.container
},
disable: function(t) {
t = this._nodeToButton(t);
return y(t.node).addClass(this.c.dom.button.disabled).prop("disabled", !0), this
},
destroy: function() {
y("body").off("keyup." + this.s.namespace);
for (var t = this.s.buttons.slice(), n = 0, e = t.length; n < e; n++) this.remove(t[n].node);
this.dom.container.remove();
var o = this.s.dt.settings()[0];
for (n = 0, e = o.length; n < e; n++)
if (o.inst === this) {
o.splice(n, 1);
break
} return this
},
enable: function(t, n) {
return !1 === n ? this.disable(t) : (n = this._nodeToButton(t), y(n.node).removeClass(this.c.dom.button.disabled).prop("disabled", !1), this)
},
index: function(t, n, e) {
n || (n = "", e = this.s.buttons);
for (var o = 0, i = e.length; o < i; o++) {
var s = e[o].buttons;
if (e[o].node === t) return n + o;
if (s && s.length) {
s = this.index(t, o + "-", s);
if (null !== s) return s
}
}
return null
},
name: function() {
return this.c.name
},
node: function(t) {
return t ? (t = this._nodeToButton(t), y(t.node)) : this.dom.container
},
processing: function(t, n) {
var e = this.s.dt,
o = this._nodeToButton(t);
return n === w ? y(o.node).hasClass("processing") : (y(o.node).toggleClass("processing", n), y(e.table().node()).triggerHandler("buttons-processing.dt", [n, e.button(t), e, y(t), o.conf]), this)
},
remove: function(t) {
var n = this._nodeToButton(t),
e = this._nodeToHost(t),
o = this.s.dt;
if (n.buttons.length)
for (var i = n.buttons.length - 1; 0 <= i; i--) this.remove(n.buttons[i].node);
n.conf.destroying = !0, n.conf.destroy && n.conf.destroy.call(o.button(t), o, y(t), n.conf), this._removeKey(n.conf), y(n.node).remove();
o = y.inArray(n, e);
return e.splice(o, 1), this
},
text: function(t, n) {
function e(t) {
return "function" == typeof t ? t(i, s, o.conf) : t
}
var o = this._nodeToButton(t),
t = o.textNode,
i = this.s.dt,
s = y(o.node);
return n === w ? e(o.conf.text) : (o.conf.text = n, t.html(e(n)), this)
},
_constructor: function() {
var e = this,
t = this.s.dt,
o = t.settings()[0],
n = this.c.buttons;
o._buttons || (o._buttons = []), o._buttons.push({
inst: this,
name: this.c.name
});
for (var i = 0, s = n.length; i < s; i++) this.add(n[i]);
t.on("destroy", function(t, n) {
n === o && e.destroy()
}), y("body").on("keyup." + this.s.namespace, function(t) {
var n;
x.activeElement && x.activeElement !== x.body || (n = String.fromCharCode(t.keyCode).toLowerCase(), -1 !== e.s.listenKeys.toLowerCase().indexOf(n) && e._keypress(n, t))
})
},
_addKey: function(t) {
t.key && (this.s.listenKeys += (y.isPlainObject(t.key) ? t.key : t).key)
},
_draw: function(t, n) {
t || (t = this.dom.container, n = this.s.buttons), t.children().detach();
for (var e = 0, o = n.length; e < o; e++) t.append(n[e].inserter), t.append(" "), n[e].buttons && n[e].buttons.length && this._draw(n[e].collection, n[e].buttons)
},
_expandButton: function(t, n, e, o, i, s, a) {
for (var r, l = this.s.dt, c = this.c.dom.collection, u = Array.isArray(n) ? n : [n], d = 0, f = (u = n === w ? Array.isArray(e) ? e : [e] : u).length; d < f; d++) {
var p = this._resolveExtends(u[d]);
if (p)
if (r = !(!p.config || !p.config.split), Array.isArray(p)) this._expandButton(t, p, h !== w && h.conf !== w ? h.conf.split : w, o, a !== w && a.split !== w, s, a);
else {
var h = this._buildButton(p, o, p.split !== w || p.config !== w && p.config.split !== w, i);
if (h) {
if (s !== w && null !== s ? (t.splice(s, 0, h), s++) : t.push(h), h.conf.buttons && (h.collection = y("<" + c.container.content.tag + "/>"), h.conf._collection = h.collection, y(h.node).append(c.action.dropHtml), this._expandButton(h.buttons, h.conf.buttons, h.conf.split, !r, r, s, h.conf)), h.conf.split) {
h.collection = y("<" + c.container.tag + "/>"), h.conf._collection = h.collection;
for (var b = 0; b < h.conf.split.length; b++) {
var g = h.conf.split[b];
"object" == typeof g && (g.parent = a, g.collectionLayout === w && (g.collectionLayout = h.conf.collectionLayout), g.dropup === w && (g.dropup = h.conf.dropup), g.fade === w) && (g.fade = h.conf.fade)
}
this._expandButton(h.buttons, h.conf.buttons, h.conf.split, !r, r, s, h.conf)
}
h.conf.parent = a, p.init && p.init.call(l.button(h.node), l, y(h.node), p)
}
}
}
},
_buildButton: function(n, t, e, o) {
function i(t) {
return "function" == typeof t ? t(u, l, n) : t
}
var s, a, r, l, c = this.c.dom,
u = this.s.dt,
d = y.extend(!0, {}, c.button);
if (t && e && c.collection.split ? y.extend(!0, d, c.collection.split.action) : o || t ? y.extend(!0, d, c.collection.button) : e && y.extend(!0, d, c.split.button), n.spacer) return c = y("<" + d.spacer.tag + "/>").addClass("dt-button-spacer " + n.style + " " + d.spacer.className).html(i(n.text)), {
conf: n,
node: c,
inserter: c,
buttons: [],
inCollection: t,
isSplit: e,
collection: null,
textNode: c
};
if (n.available && !n.available(u, n) && !n.hasOwnProperty("html")) return !1;
n.hasOwnProperty("html") ? l = y(n.html) : (a = function(t, n, e, o) {
o.action.call(n.button(e), t, n, e, o), y(n.table().node()).triggerHandler("buttons-action.dt", [n.button(e), n, e, o])
}, c = n.tag || d.tag, r = n.clickBlurs === w || n.clickBlurs, l = y("<" + c + "/>").addClass(d.className).attr("tabindex", this.s.dt.settings()[0].iTabIndex).attr("aria-controls", this.s.dt.table().node().id).on("click.dtb", function(t) {
t.preventDefault(), !l.hasClass(d.disabled) && n.action && a(t, u, l, n), r && l.trigger("blur")
}).on("keypress.dtb", function(t) {
13 === t.keyCode && (t.preventDefault(), !l.hasClass(d.disabled)) && n.action && a(t, u, l, n)
}), "a" === c.toLowerCase() && l.attr("href", "#"), "button" === c.toLowerCase() && l.attr("type", "button"), s = d.liner.tag ? (c = y("<" + d.liner.tag + "/>").html(i(n.text)).addClass(d.liner.className), "a" === d.liner.tag.toLowerCase() && c.attr("href", "#"), l.append(c), c) : (l.html(i(n.text)), l), !1 === n.enabled && l.addClass(d.disabled), n.className && l.addClass(n.className), n.titleAttr && l.attr("title", i(n.titleAttr)), n.attr && l.attr(n.attr), n.namespace || (n.namespace = ".dt-button-" + C++), n.config !== w && n.config.split && (n.split = n.config.split));
var f, p, h, b, g, m, c = this.c.dom.buttonContainer,
c = c && c.tag ? y("<" + c.tag + "/>").addClass(c.className).append(l) : l;
return this._addKey(n), this.c.buttonCreated && (c = this.c.buttonCreated(n, c)), e && (p = (f = t ? y.extend(!0, this.c.dom.split, this.c.dom.collection.split) : this.c.dom.split).wrapper, h = y("<" + p.tag + "/>").addClass(p.className).append(l), b = y.extend(n, {
align: f.dropdown.align,
attr: {
"aria-haspopup": "dialog",
"aria-expanded": !1
},
className: f.dropdown.className,
closeButton: !1,
splitAlignClass: f.dropdown.splitAlignClass,
text: f.dropdown.text
}), this._addKey(b), g = function(t, n, e, o) {
_.split.action.call(n.button(h), t, n, e, o), y(n.table().node()).triggerHandler("buttons-action.dt", [n.button(e), n, e, o]), e.attr("aria-expanded", !0)
}, m = y('<button class="' + f.dropdown.className + ' dt-button"></button>').html(f.dropdown.dropHtml).on("click.dtb", function(t) {
t.preventDefault(), t.stopPropagation(), m.hasClass(d.disabled) || g(t, u, m, b), r && m.trigger("blur")
}).on("keypress.dtb", function(t) {
13 === t.keyCode && (t.preventDefault(), m.hasClass(d.disabled) || g(t, u, m, b))
}), 0 === n.split.length && m.addClass("dtb-hide-drop"), h.append(m).attr(b.attr)), {
conf: n,
node: (e ? h : l).get(0),
inserter: e ? h : c,
buttons: [],
inCollection: t,
isSplit: e,
inSplit: o,
collection: null,
textNode: s
}
},
_nodeToButton: function(t, n) {
for (var e = 0, o = (n = n || this.s.buttons).length; e < o; e++) {
if (n[e].node === t) return n[e];
if (n[e].buttons.length) {
var i = this._nodeToButton(t, n[e].buttons);
if (i) return i
}
}
},
_nodeToHost: function(t, n) {
for (var e = 0, o = (n = n || this.s.buttons).length; e < o; e++) {
if (n[e].node === t) return n;
if (n[e].buttons.length) {
var i = this._nodeToHost(t, n[e].buttons);
if (i) return i
}
}
},
_keypress: function(s, a) {
var r;
a._buttonsHandled || (r = function(t) {
for (var n, e, o = 0, i = t.length; o < i; o++) n = t[o].conf, e = t[o].node, !n.key || n.key !== s && (!y.isPlainObject(n.key) || n.key.key !== s || n.key.shiftKey && !a.shiftKey || n.key.altKey && !a.altKey || n.key.ctrlKey && !a.ctrlKey || n.key.metaKey && !a.metaKey) || (a._buttonsHandled = !0, y(e).click()), t[o].buttons.length && r(t[o].buttons)
})(this.s.buttons)
},
_removeKey: function(t) {
var n;
t.key && (t = (y.isPlainObject(t.key) ? t.key : t).key, n = this.s.listenKeys.split(""), t = y.inArray(t, n), n.splice(t, 1), this.s.listenKeys = n.join(""))
},
_resolveExtends: function(e) {
function t(t) {
for (var n = 0; !y.isPlainObject(t) && !Array.isArray(t);) {
if (t === w) return;
if ("function" == typeof t) {
if (!(t = t.call(i, s, e))) return !1
} else if ("string" == typeof t) {
if (!_[t]) return {
html: t
};
t = _[t]
}
if (30 < ++n) throw "Buttons: Too many iterations"
}
return Array.isArray(t) ? t : y.extend({}, t)
}
var n, o, i = this,
s = this.s.dt;
for (e = t(e); e && e.extend;) {
if (!_[e.extend]) throw "Cannot extend unknown button type: " + e.extend;
var a = t(_[e.extend]);
if (Array.isArray(a)) return a;
if (!a) return !1;
var r = a.className;
e.config !== w && a.config !== w && (e.config = y.extend({}, a.config, e.config)), e = y.extend({}, a, e), r && e.className !== r && (e.className = r + " " + e.className), e.extend = a.extend
}
var l = e.postfixButtons;
if (l)
for (e.buttons || (e.buttons = []), n = 0, o = l.length; n < o; n++) e.buttons.push(l[n]);
var c = e.prefixButtons;
if (c)
for (e.buttons || (e.buttons = []), n = 0, o = c.length; n < o; n++) e.buttons.splice(n, 0, c[n]);
return e
},
_popover: function(o, t, n, e) {
function i() {
h = !0, k(y(g), b.fade, function() {
y(this).detach()
}), y(f.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()).attr("aria-expanded", "false"), y("div.dt-button-background").off("click.dtb-collection"), B.background(!1, b.backgroundClassName, b.fade, m), y(v).off("resize.resize.dtb-collection"), y("body").off(".dtb-collection"), f.off("buttons-action.b-internal"), f.off("destroy")
}
var s, a, r, l, c, u, d, f = t,
p = this.c,
h = !1,
b = y.extend({
align: "button-left",
autoClose: !1,
background: !0,
backgroundClassName: "dt-button-background",
closeButton: !0,
containerClassName: p.dom.collection.container.className,
contentClassName: p.dom.collection.container.content.className,
collectionLayout: "",
collectionTitle: "",
dropup: !1,
fade: 400,
popoverTitle: "",
rightAlignClassName: "dt-button-right",
tag: p.dom.collection.container.tag
}, n),
g = b.tag + "." + b.containerClassName.replace(/ /g, "."),
m = t.node();
!1 === o ? i() : ((p = y(f.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes())).length && (m.closest(g).length && (m = p.eq(0)), i()), n = y(".dt-button", o).length, p = "", 3 === n ? p = "dtb-b3" : 2 === n ? p = "dtb-b2" : 1 === n && (p = "dtb-b1"), s = y("<" + b.tag + "/>").addClass(b.containerClassName).addClass(b.collectionLayout).addClass(b.splitAlignClass).addClass(p).css("display", "none").attr({
"aria-modal": !0,
role: "dialog"
}), o = y(o).addClass(b.contentClassName).attr("role", "menu").appendTo(s), m.attr("aria-expanded", "true"), m.parents("body")[0] !== x.body && (m = x.body.lastChild), b.popoverTitle ? s.prepend('<div class="dt-button-collection-title">' + b.popoverTitle + "</div>") : b.collectionTitle && s.prepend('<div class="dt-button-collection-title">' + b.collectionTitle + "</div>"), b.closeButton && s.prepend('<div class="dtb-popover-close">&times;</div>').addClass("dtb-collection-closeable"), A(s.insertAfter(m), b.fade), n = y(t.table().container()), d = s.css("position"), "container" !== b.span && "dt-container" !== b.align || (m = m.parent(), s.css("width", n.width())), "absolute" === d ? (p = y(m[0].offsetParent), t = m.position(), n = m.offset(), a = p.offset(), r = p.position(), l = v.getComputedStyle(p[0]), a.height = p.outerHeight(), a.width = p.width() + parseFloat(l.paddingLeft), a.right = a.left + a.width, a.bottom = a.top + a.height, p = t.top + m.outerHeight(), c = t.left, s.css({
top: p,
left: c
}), l = v.getComputedStyle(s[0]), (u = s.offset()).height = s.outerHeight(), u.width = s.outerWidth(), u.right = u.left + u.width, u.bottom = u.top + u.height, u.marginTop = parseFloat(l.marginTop), u.marginBottom = parseFloat(l.marginBottom), b.dropup && (p = t.top - u.height - u.marginTop - u.marginBottom), "button-right" !== b.align && !s.hasClass(b.rightAlignClassName) || (c = t.left - u.width + m.outerWidth()), "dt-container" !== b.align && "container" !== b.align || (c = c < t.left ? -t.left : c) + u.width > a.width && (c = a.width - u.width), r.left + c + u.width > y(v).width() && (c = y(v).width() - u.width - r.left), n.left + c < 0 && (c = -n.left), r.top + p + u.height > y(v).height() + y(v).scrollTop() && (p = t.top - u.height - u.marginTop - u.marginBottom), r.top + p < y(v).scrollTop() && (p = t.top + m.outerHeight()), s.css({
top: p,
left: c
})) : ((d = function() {
var t = y(v).height() / 2,
n = s.height() / 2;
s.css("marginTop", -1 * (n = t < n ? t : n))
})(), y(v).on("resize.dtb-collection", function() {
d()
})), b.background && B.background(!0, b.backgroundClassName, b.fade, b.backgroundHost || m), y("div.dt-button-background").on("click.dtb-collection", function() { }), b.autoClose && setTimeout(function() {
f.on("buttons-action.b-internal", function(t, n, e, o) {
o[0] !== m[0] && i()
})
}, 0), y(s).trigger("buttons-popover.dt"), f.on("destroy", i), setTimeout(function() {
h = !1, y("body").on("click.dtb-collection", function(t) {
var n, e;
!h && (n = y.fn.addBack ? "addBack" : "andSelf", e = y(t.target).parent()[0], !y(t.target).parents()[n]().filter(o).length && !y(e).hasClass("dt-buttons") || y(t.target).hasClass("dt-button-background")) && i()
}).on("keyup.dtb-collection", function(t) {
27 === t.keyCode && i()
}).on("keydown.dtb-collection", function(t) {
var n = y("a, button", o),
e = x.activeElement;
9 === t.keyCode && (-1 === n.index(e) ? (n.first().focus(), t.preventDefault()) : t.shiftKey ? e === n[0] && (n.last().focus(), t.preventDefault()) : e === n.last()[0] && (n.first().focus(), t.preventDefault()))
})
}, 0))
}
}), B.background = function(t, n, e, o) {
e === w && (e = 400), o = o || x.body, t ? A(y("<div/>").addClass(n).css("display", "none").insertAfter(o), e) : k(y("div." + n), e, function() {
y(this).removeClass(n).remove()
})
}, B.instanceSelector = function(t, i) {
var s, a, r;
return t === w || null === t ? y.map(i, function(t) {
return t.inst
}) : (s = [], a = y.map(i, function(t) {
return t.name
}), (r = function(t) {
var n;
if (Array.isArray(t))
for (var e = 0, o = t.length; e < o; e++) r(t[e]);
else "string" == typeof t ? -1 !== t.indexOf(",") ? r(t.split(",")) : -1 !== (n = y.inArray(t.trim(), a)) && s.push(i[n].inst) : "number" == typeof t ? s.push(i[t].inst) : "object" == typeof t && s.push(t)
})(t), s)
}, B.buttonSelector = function(t, n) {
for (var c = [], u = function(t, n, e) {
for (var o, i, s = 0, a = n.length; s < a; s++)(o = n[s]) && (t.push({
node: o.node,
name: o.conf.name,
idx: i = e !== w ? e + s : s + ""
}), o.buttons) && u(t, o.buttons, i + "-")
}, d = function(t, n) {
var e = [],
o = (u(e, n.s.buttons), y.map(e, function(t) {
return t.node
}));
if (Array.isArray(t) || t instanceof y)
for (s = 0, a = t.length; s < a; s++) d(t[s], n);
else if (null === t || t === w || "*" === t)
for (s = 0, a = e.length; s < a; s++) c.push({
inst: n,
node: e[s].node
});
else if ("number" == typeof t) n.s.buttons[t] && c.push({
inst: n,
node: n.s.buttons[t].node
});
else if ("string" == typeof t)
if (-1 !== t.indexOf(","))
for (var i = t.split(","), s = 0, a = i.length; s < a; s++) d(i[s].trim(), n);
else if (t.match(/^\d+(\-\d+)*$/)) {
var r = y.map(e, function(t) {
return t.idx
});
c.push({
inst: n,
node: e[y.inArray(t, r)].node
})
} else if (-1 !== t.indexOf(":name")) {
var l = t.replace(":name", "");
for (s = 0, a = e.length; s < a; s++) e[s].name === l && c.push({
inst: n,
node: e[s].node
})
} else y(o).filter(t).each(function() {
c.push({
inst: n,
node: this
})
});
else "object" == typeof t && t.nodeName && -1 !== (r = y.inArray(t, o)) && c.push({
inst: n,
node: o[r]
})
}, e = 0, o = t.length; e < o; e++) {
var i = t[e];
d(n, i)
}
return c
}, B.stripData = function(t, n) {
return "string" == typeof t && (t = (t = t.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).replace(/<!\-\-.*?\-\->/g, ""), n && !n.stripHtml || (t = t.replace(/<[^>]*>/g, "")), n && !n.trim || (t = t.replace(/^\s+|\s+$/g, "")), n && !n.stripNewlines || (t = t.replace(/\n/g, " ")), !n || n.decodeEntities) && (l.innerHTML = t, t = l.value), t
}, B.defaults = {
buttons: ["copy", "excel", "csv", "pdf", "print"],
name: "main",
tabIndex: 0,
dom: {
container: {
tag: "div",
className: "dt-buttons"
},
collection: {
action: {
dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>'
},
container: {
className: "dt-button-collection",
content: {
className: "",
tag: "div"
},
tag: "div"
}
},
button: {
tag: "button",
className: "dt-button",
active: "dt-button-active",
disabled: "disabled",
spacer: {
className: "dt-button-spacer",
tag: "span"
},
liner: {
tag: "span",
className: ""
}
},
split: {
action: {
className: "dt-button-split-drop-button dt-button",
tag: "button"
},
dropdown: {
align: "split-right",
className: "dt-button-split-drop",
dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>',
splitAlignClass: "dt-button-split-left",
tag: "button"
},
wrapper: {
className: "dt-button-split",
tag: "div"
}
}
}
}, y.extend(_, {
collection: {
text: function(t) {
return t.i18n("buttons.collection", "Collection")
},
className: "buttons-collection",
closeButton: !(B.version = "2.4.1"),
init: function(t, n, e) {
n.attr("aria-expanded", !1)
},
action: function(t, n, e, o) {
o._collection.parents("body").length ? this.popover(!1, o) : this.popover(o._collection, o), "keypress" === t.type && y("a, button", o._collection).eq(0).focus()
},
attr: {
"aria-haspopup": "dialog"
}
},
split: {
text: function(t) {
return t.i18n("buttons.split", "Split")
},
className: "buttons-split",
closeButton: !1,
init: function(t, n, e) {
return n.attr("aria-expanded", !1)
},
action: function(t, n, e, o) {
this.popover(o._collection, o)
},
attr: {
"aria-haspopup": "dialog"
}
},
copy: function(t, n) {
if (_.copyHtml5) return "copyHtml5"
},
csv: function(t, n) {
if (_.csvHtml5 && _.csvHtml5.available(t, n)) return "csvHtml5"
},
excel: function(t, n) {
if (_.excelHtml5 && _.excelHtml5.available(t, n)) return "excelHtml5"
},
pdf: function(t, n) {
if (_.pdfHtml5 && _.pdfHtml5.available(t, n)) return "pdfHtml5"
},
pageLength: function(t) {
var n = t.settings()[0].aLengthMenu,
e = [],
o = [];
if (Array.isArray(n[0])) e = n[0], o = n[1];
else
for (var i = 0; i < n.length; i++) {
var s = n[i];
y.isPlainObject(s) ? (e.push(s.value), o.push(s.label)) : (e.push(s), o.push(s))
}
return {
extend: "collection",
text: function(t) {
return t.i18n("buttons.pageLength", {
"-1": "Show all rows",
_: "Show %d rows"
}, t.page.len())
},
className: "buttons-page-length",
autoClose: !0,
buttons: y.map(e, function(s, t) {
return {
text: o[t],
className: "button-page-length",
action: function(t, n) {
n.page.len(s).draw()
},
init: function(t, n, e) {
function o() {
i.active(t.page.len() === s)
}
var i = this;
t.on("length.dt" + e.namespace, o), o()
},
destroy: function(t, n, e) {
t.off("length.dt" + e.namespace)
}
}
}),
init: function(t, n, e) {
var o = this;
t.on("length.dt" + e.namespace, function() {
o.text(e.text)
})
},
destroy: function(t, n, e) {
t.off("length.dt" + e.namespace)
}
}
},
spacer: {
style: "empty",
spacer: !0,
text: function(t) {
return t.i18n("buttons.spacer", "")
}
}
}), e.Api.register("buttons()", function(n, e) {
e === w && (e = n, n = w), this.selector.buttonGroup = n;
var t = this.iterator(!0, "table", function(t) {
if (t._buttons) return B.buttonSelector(B.instanceSelector(n, t._buttons), e)
}, !0);
return t._groupSelector = n, t
}), e.Api.register("button()", function(t, n) {
t = this.buttons(t, n);
return 1 < t.length && t.splice(1, t.length), t
}), e.Api.registerPlural("buttons().active()", "button().active()", function(n) {
return n === w ? this.map(function(t) {
return t.inst.active(t.node)
}) : this.each(function(t) {
t.inst.active(t.node, n)
})
}), e.Api.registerPlural("buttons().action()", "button().action()", function(n) {
return n === w ? this.map(function(t) {
return t.inst.action(t.node)
}) : this.each(function(t) {
t.inst.action(t.node, n)
})
}), e.Api.registerPlural("buttons().collectionRebuild()", "button().collectionRebuild()", function(e) {
return this.each(function(t) {
for (var n = 0; n < e.length; n++) "object" == typeof e[n] && (e[n].parentConf = t);
t.inst.collectionRebuild(t.node, e)
})
}), e.Api.register(["buttons().enable()", "button().enable()"], function(n) {
return this.each(function(t) {
t.inst.enable(t.node, n)
})
}), e.Api.register(["buttons().disable()", "button().disable()"], function() {
return this.each(function(t) {
t.inst.disable(t.node)
})
}), e.Api.register("button().index()", function() {
var n = null;
return this.each(function(t) {
t = t.inst.index(t.node);
null !== t && (n = t)
}), n
}), e.Api.registerPlural("buttons().nodes()", "button().node()", function() {
var n = y();
return y(this.each(function(t) {
n = n.add(t.inst.node(t.node))
})), n
}), e.Api.registerPlural("buttons().processing()", "button().processing()", function(n) {
return n === w ? this.map(function(t) {
return t.inst.processing(t.node)
}) : this.each(function(t) {
t.inst.processing(t.node, n)
})
}), e.Api.registerPlural("buttons().text()", "button().text()", function(n) {
return n === w ? this.map(function(t) {
return t.inst.text(t.node)
}) : this.each(function(t) {
t.inst.text(t.node, n)
})
}), e.Api.registerPlural("buttons().trigger()", "button().trigger()", function() {
return this.each(function(t) {
t.inst.node(t.node).trigger("click")
})
}), e.Api.register("button().popover()", function(n, e) {
return this.map(function(t) {
return t.inst._popover(n, this.button(this[0].node), e)
})
}), e.Api.register("buttons().containers()", function() {
var i = y(),
s = this._groupSelector;
return this.iterator(!0, "table", function(t) {
if (t._buttons)
for (var n = B.instanceSelector(s, t._buttons), e = 0, o = n.length; e < o; e++) i = i.add(n[e].container())
}), i
}), e.Api.register("buttons().container()", function() {
return this.containers().eq(0)
}), e.Api.register("button().add()", function(t, n, e) {
var o = this.context;
return o.length && (o = B.instanceSelector(this._groupSelector, o[0]._buttons)).length && o[0].add(n, t, e), this.button(this._groupSelector, t)
}), e.Api.register("buttons().destroy()", function() {
return this.pluck("inst").unique().each(function(t) {
t.destroy()
}), this
}), e.Api.registerPlural("buttons().remove()", "buttons().remove()", function() {
return this.each(function(t) {
t.inst.remove(t.node)
}), this
}), e.Api.register("buttons.info()", function(t, n, e) {
var o = this;
return !1 === t ? (this.off("destroy.btn-info"), k(y("#datatables_buttons_info"), 400, function() {
y(this).remove()
}), clearTimeout(i), i = null) : (i && clearTimeout(i), y("#datatables_buttons_info").length && y("#datatables_buttons_info").remove(), t = t ? "<h2>" + t + "</h2>" : "", A(y('<div id="datatables_buttons_info" class="dt-button-info"/>').html(t).append(y("<div/>")["string" == typeof n ? "html" : "append"](n)).css("display", "none").appendTo("body")), e !== w && 0 !== e && (i = setTimeout(function() {
o.buttons.info(!1)
}, e)), this.on("destroy.btn-info", function() {
o.buttons.info(!1)
})), this
}), e.Api.register("buttons.exportData()", function(t) {
if (this.context.length) return c(new e.Api(this.context[0]), t)
}), e.Api.register("buttons.exportInfo()", function(t) {
return {
filename: n(t = t || {}),
title: a(t),
messageTop: r(this, t.message || t.messageTop, "top"),
messageBottom: r(this, t.messageBottom, "bottom")
}
});
var i, n = function(t) {
var n;
return (n = "function" == typeof (n = "*" === t.filename && "*" !== t.title && t.title !== w && null !== t.title && "" !== t.title ? t.title : t.filename) ? n() : n) === w || null === n ? null : (n = (n = -1 !== n.indexOf("*") ? n.replace("*", y("head > title").text()).trim() : n).replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "")) + (s(t.extension) || "")
},
s = function(t) {
return null === t || t === w ? null : "function" == typeof t ? t() : t
},
a = function(t) {
t = s(t.title);
return null === t ? null : -1 !== t.indexOf("*") ? t.replace("*", y("head > title").text() || "Exported data") : t
},
r = function(t, n, e) {
n = s(n);
return null === n ? null : (t = y("caption", t.table().container()).eq(0), "*" === n ? t.css("caption-side") !== e ? null : t.length ? t.text() : "" : n)
},
l = y("<textarea/>")[0],
c = function(e, t) {
for (var o = y.extend(!0, {}, {
rows: null,
columns: "",
modifier: {
search: "applied",
order: "applied"
},
orthogonal: "display",
stripHtml: !0,
stripNewlines: !0,
decodeEntities: !0,
trim: !0,
format: {
header: function(t) {
return B.stripData(t, o)
},
footer: function(t) {
return B.stripData(t, o)
},
body: function(t) {
return B.stripData(t, o)
}
},
customizeData: null
}, t), t = e.columns(o.columns).indexes().map(function(t) {
var n = e.column(t).header();
return o.format.header(n.innerHTML, t, n)
}).toArray(), n = e.table().footer() ? e.columns(o.columns).indexes().map(function(t) {
var n = e.column(t).footer();
return o.format.footer(n ? n.innerHTML : "", t, n)
}).toArray() : null, i = y.extend({}, o.modifier), i = (e.select && "function" == typeof e.select.info && i.selected === w && e.rows(o.rows, y.extend({
selected: !0
}, i)).any() && y.extend(i, {
selected: !0
}), e.rows(o.rows, i).indexes().toArray()), i = e.cells(i, o.columns), s = i.render(o.orthogonal).toArray(), a = i.nodes().toArray(), r = t.length, l = [], c = 0, u = 0, d = 0 < r ? s.length / r : 0; u < d; u++) {
for (var f = [r], p = 0; p < r; p++) f[p] = o.format.body(s[c], u, p, a[c]), c++;
l[u] = f
}
i = {
header: t,
footer: n,
body: l
};
return o.customizeData && o.customizeData(i), i
};
function t(t, n) {
t = new e.Api(t), n = n || t.init().buttons || e.defaults.buttons;
return new B(t, n).container()
}
return y.fn.dataTable.Buttons = B, y.fn.DataTable.Buttons = B, y(x).on("init.dt plugin-init.dt", function(t, n) {
"dt" === t.namespace && (t = n.oInit.buttons || e.defaults.buttons) && !n._buttons && new B(n, t).container()
}), e.ext.feature.push({
fnInit: t,
cFeature: "B"
}), e.ext.features && e.ext.features.register("buttons", t), e
});

6995
src/main/resources/static/assets/font-awesome-6.5.1/css/all.min.css
File diff suppressed because it is too large
View File

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.woff2

7023
src/main/resources/static/assets/font-awesome-6.5.2/css/all.min.css
File diff suppressed because it is too large
View File

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.woff2

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.ttf

BIN
src/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.woff2

BIN
src/main/resources/static/assets/header-images/gcc1.png

After

Width: 325  |  Height: 435  |  Size: 204 KiB

3244
src/main/resources/static/assets/jquery-3.6.4/js/jquery-3.6.4.min.js
File diff suppressed because it is too large
View File

93
src/main/resources/static/assets/js/add-url-data-script.js

@ -0,0 +1,93 @@
// Global flags
let otpSent = false;
let isAdmin = false;
async function loadRole() {
try {
const res = await fetch(`${contextPath}api/get-role`, {
method: "POST"
});
const data = await res.json();
if (!data.authenticated) {
isAdmin = false;
return;
}
isAdmin = data.isAdmin;
} catch (e) {
// swallow network errors
console.error("Error: ", error);
}
}
// Open the login modal
function openLoginModal() {
if (isAdmin) {
// For admin → directly go to the post-verification page
window.open(`${contextPath}view-login-det`, '_blank');
return;
}
const modal = document.getElementById("login-modal");
modal.classList.remove("hide");
modal.classList.add("show");
modal.style.display = "flex"; // make it visible immediately before animation
}
// Load role when page loads
document.addEventListener("DOMContentLoaded", () => {
loadRole();
});
document.addEventListener("DOMContentLoaded", function() {
const togglePass = document.getElementById("togglePass");
const urlUsrPassInput = document.getElementById("urlDataUsrPass");
const form = document.getElementById("frm-url-det");
// safety check
if (!togglePass || !urlUsrPassInput) {
return;
}
// if (!urlUsrPassInput) return;
// --- Toggle Password Visibility ---
togglePass.addEventListener("click", function() {
const isPassword = urlUsrPassInput.type === "password";
urlUsrPassInput.type = isPassword ? "text" : "password";
togglePass.classList.toggle("fa-eye");
togglePass.classList.toggle("fa-eye-slash");
});
// --- Form submission logic ---
form.addEventListener("submit", function(e) {
e.preventDefault();
const formData = new FormData(form);
fetch(form.getAttribute("action"), {
method: "POST",
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok: " + response.statusText);
}
return response.json();
})
.then(data => {
if (data.resStat) {
alert("Success: " + data.res);
form.reset();
} else {
alert("Failed: " + data.res);
}
})
.catch(error => {
console.error("Error adding url:", error);
alert("Error adding url. Please try again.");
});
});
});

23
src/main/resources/static/assets/js/fetch-csrf-script.js

@ -0,0 +1,23 @@
const csrfMeta = document.querySelector('meta[name="_csrf"]');
const csrfHeaderMeta = document.querySelector('meta[name="_csrf_header"]');
const csrfToken = csrfMeta ? csrfMeta.content : null;
const csrfHeader = csrfHeaderMeta ? csrfHeaderMeta.content : null;
// Save original fetch
const originalFetch = window.fetch;
window.fetch = (url, options = {}) => {
// Default method is GET if not provided
const method = (options.method || "GET").toUpperCase();
// Only attach CSRF for unsafe methods
if (["POST", "PUT", "DELETE", "PATCH"].includes(method)) {
options.headers = {
...options.headers,
[csrfHeader]: csrfToken
};
}
return originalFetch(url, options);
};

3
src/main/resources/static/assets/js/go-back-script.js

@ -0,0 +1,3 @@
function goBack() {
window.history.back();
}

426
src/main/resources/static/assets/js/home-script.js

@ -0,0 +1,426 @@
// Global flags
let otpSent = false;
var isAdmin = window.isAdmin || false;
async function clearBackendOtpSession() {
try {
await fetch(`${contextPath}api/clr-otp-ses`, { method: "POST" });
} catch (e) {
console.error("Failed to clear backend OTP session", e);
}
}
async function reloadMobileDropdown() {
try {
const res = await fetch(`${contextPath}api/get-mob-num`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: "{}" // empty JSON payload
});
const mobNumInput = document.getElementById("mobNum");
mobNumInput.innerHTML = ""; // clear all old options
if (!res.ok) {
mobNumInput.innerHTML = '<option value="">-- Failed to load numbers --</option>';
console.error(`Failed to fetch mobile numbers: ${res.status} ${res.statusText}`);
return;
}
if (res.status === 204) {
// No data returned
mobNumInput.innerHTML = '<option value="">-- No mobile numbers available --</option>';
return;
}
const data = await res.json();
data.forEach(item => {
const opt = document.createElement("option");
opt.value = item.mobNumId; // new UUID
opt.textContent = item.mobNum; // masked number
mobNumInput.appendChild(opt);
});
} catch (err) {
console.error("Error loading mobile numbers:", err);
const mobNumInput = document.getElementById("mobNum");
mobNumInput.innerHTML = '<option value="">-- Failed to load numbers --</option>';
}
}
async function loadRole() {
try {
const res = await fetch(`${contextPath}api/get-role`, {
method: "POST"
});
const data = await res.json();
if (!data.authenticated) {
isAdmin = false;
return;
}
isAdmin = data.isAdmin;
} catch (e) {
// swallow network errors
console.error("Error: ", error);
}
}
async function loadMobNum() {
try {
const res = await fetch(`${contextPath}api/get-mob-num`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: "{}"
});
const mobSelect = document.getElementById("mobNum");
mobSelect.innerHTML = '<option value="">-- Select Mobile Number --</option>';
if (res.status === 204) {
// No data returned
mobSelect.innerHTML = '<option value="">-- No mobile numbers available --</option>';
return;
}
if (!res.ok) {
throw new Error(`Server error: ${res.status}`);
}
const data = await res.json();
data.forEach(item => {
const opt = document.createElement("option");
opt.value = item.mobNumId; // mobNumId for backend
opt.textContent = item.mobNum; // Masked number for display
mobSelect.appendChild(opt);
});
} catch (err) {
console.error("Failed to load mobile numbers", err);
const mobSelect = document.getElementById("mobNum");
mobSelect.innerHTML = '<option value="">-- Failed to load numbers --</option>';
}
}
// Open the login modal
function openLoginModal() {
if (isAdmin) {
// For admin → directly go to the post-verification page
window.open(`${contextPath}view-login-det`, '_blank');
return;
}
const modal = document.getElementById("login-modal");
modal.classList.remove("hide");
modal.classList.add("show");
modal.style.display = "flex"; // make it visible immediately before animation
// Load only when modal is opened
loadMobNum();
}
// Close the modal and reset its state
async function closeLoginModal() {
const modal = document.getElementById("login-modal");
const otpForm = document.getElementById("otp-form");
const otpSection = document.getElementById("otp-section");
const sendOtpBtn = document.getElementById("send-otp-btn");
const messageEl = document.getElementById("otp-message");
const mobInput = document.getElementById("mobNum");
const otpInput = document.getElementById("otp");
// Reset form and state
if (otpForm) otpForm.reset(); // Reset the form fields
if (otpSection) otpSection.style.display = "none"; // Hide OTP section
if (sendOtpBtn) {
sendOtpBtn.textContent = "Get OTP"; // Reset button text
sendOtpBtn.disabled = false; // Enable the button
}
if (messageEl) {
messageEl.textContent = ""; // Clear any message
messageEl.style.color = "#333"; // Reset the default message color
}
// Reset mobile number input and OTP input
if (mobInput) mobInput.value = '';
if (otpInput) otpInput.value = '';
// Reset JS flag
otpSent = false;
await clearBackendOtpSession();
// Animate and hide the modal
modal.classList.remove("show");
modal.classList.add("hide");
// Handle the animation and hide modal after animation ends
modal.addEventListener(
"animationend",
function handler() {
if (modal.classList.contains("hide")) {
modal.style.display = "none"; // Hide modal after animation
}
modal.removeEventListener("animationend", handler); // Clean up the event listener
}
);
}
// Close the modal when clicking outside of it
window.onclick = function (event) {
const modal = document.getElementById("login-modal");
if (event.target === modal) {
closeLoginModal(); // Also reset everything when clicking outside
}
};
// Load role when page loads
document.addEventListener("DOMContentLoaded", () => {
loadRole();
});
// OTP Form Submission Handling
document.addEventListener("DOMContentLoaded", () => {
const otpForm = document.getElementById("otp-form");
if (!otpForm) return;
const messageEl = document.getElementById("otp-message");
const otpSection = document.getElementById("otp-section");
const sendOtpBtn = document.getElementById("send-otp-btn");
const mobNumInput = document.getElementById("mobNum");
const otpInput = document.getElementById("otp");
otpForm.addEventListener("submit", async (e) => {
e.preventDefault();
messageEl.textContent = "";
messageEl.style.color = "inherit";
const mobId = mobNumInput.value; // mobNumId
const selectedText = mobNumInput.options[mobNumInput.selectedIndex]?.text;
if (!mobId) {
messageEl.textContent = "Please select a mobile number.";
messageEl.style.color = "red";
return;
}
sendOtpBtn.disabled = true;
try {
if (!otpSent) {
// === GET OTP PHASE ===
sendOtpBtn.textContent = "Sending...";
const formData = new FormData();
formData.append("mobNumId", mobId);
const res = await fetch(`${contextPath}api/get-otp`, {
method: "POST",
body: formData, // sends as multipart/form-data
});
const data = await res.json();
if (res.ok && data.status === "success") {
messageEl.textContent = data.message || "OTP sent successfully!";
messageEl.style.color = "green";
otpSection.style.display = "block";
sendOtpBtn.textContent = "Verify OTP";
otpSent = true;
} else {
// backend may respond with 400 or 500
messageEl.textContent = data.message || "Failed to send OTP.";
messageEl.style.color = "red";
sendOtpBtn.textContent = "Get OTP";
}
} else {
// === VERIFY OTP PHASE ===
const enteredOtp = otpInput.value.trim();
if (!enteredOtp) {
messageEl.textContent = "Please enter the OTP.";
messageEl.style.color = "red";
return;
}
sendOtpBtn.textContent = "Verifying...";
const formData = new FormData();
formData.append("mobNumId", mobId);
formData.append("otp", enteredOtp);
const res = await fetch(`${contextPath}api/verify-otp`, {
method: "POST",
body: formData, // sends as multipart/form-data
});
const data = await res.json();
if (res.ok && data.resStat) {
// OTP verified successfully
messageEl.textContent = data.res || "OTP verified successfully!";
messageEl.style.color = "green";
otpSection.style.display = "none";
sendOtpBtn.disabled = true;
await reloadMobileDropdown();
// Close modal, then redirect to home page
setTimeout(() => {
closeLoginModal();
window.location.href = contextPath;
}, 500);
} else {
// OTP verification failed or session expired
messageEl.textContent = data.res || "OTP verification failed. Please request a new one.";
messageEl.style.color = "red";
sendOtpBtn.textContent = "Get OTP";
otpSection.style.display = "none"; // hide OTP field
otpSent = false; // reset flag → next click sends a new OTP
otpInput.value = ""; // clear OTP field only
await reloadMobileDropdown();
// If session expired, allow new OTP request
if (data.res && data.res.includes("Session expired")) {
mobNumInput.disabled = false; // ensure user can still use the same number
}
}
}
} catch (err) {
console.error("Error:", err);
messageEl.textContent = "Something went wrong. Please try again.";
messageEl.style.color = "red";
sendOtpBtn.textContent = otpSent ? "Verify OTP" : "Get OTP";
} finally {
sendOtpBtn.disabled = false;
}
});
});
/* === PAGE BUTTON FILTER & SEARCH === */
document.addEventListener("DOMContentLoaded", function () {
const buttonContainer = document.querySelector(".button-row");
if (!buttonContainer) return;
const buttons = Array.from(buttonContainer.querySelectorAll(".custom-button"));
function getButtonName(btn) {
return btn.childNodes[btn.childNodes.length - 1].textContent.trim().toLowerCase();
}
const searchInput = document.getElementById("search-input");
const alphabetSelect = document.getElementById("search-input2");
const clearBtn = document.getElementById("clear-btn");
const noResultsMsg = document.getElementById("no-res-msg");
if (!searchInput || !alphabetSelect) return;
/* === Reusable Dropdown Renderer === */
function populateAlphabetDropdown(onlyVisible = false) {
const prevSelected = alphabetSelect.value; // keep previous selection
// Reset dropdown
alphabetSelect.innerHTML = '<option value="">-</option>';
const letters = new Set();
buttons.forEach(btn => {
if (onlyVisible && btn.style.display === "none") return;
const name = getButtonName(btn);
if (name.length > 0) {
letters.add(name[0].toUpperCase());
}
});
const sortedLetters = [...letters].sort();
sortedLetters.forEach(letter => {
const opt = document.createElement("option");
opt.value = letter;
opt.textContent = letter;
alphabetSelect.appendChild(opt);
});
// Restore previous selection if still valid
if (letters.has(prevSelected.toUpperCase())) {
alphabetSelect.value = prevSelected.toUpperCase();
} else {
alphabetSelect.value = "";
}
}
// Initial dropdown load (all buttons)
populateAlphabetDropdown(false);
/* === FILTER BUTTONS === */
function updateButtonVisibility() {
const query = searchInput.value.trim().toLowerCase();
const selectedLetter = alphabetSelect.value.toLowerCase();
let matchedButtons = 0;
buttons.forEach(btn => {
const name = getButtonName(btn);
const matchesLetter =
selectedLetter === "" || name.startsWith(selectedLetter);
const matchesSearch =
query === "" || name.includes(query);
if (matchesLetter && matchesSearch) {
btn.style.display = "inline-block";
matchedButtons++;
} else {
btn.style.display = "none";
}
});
noResultsMsg.style.display = matchedButtons > 0 ? "none" : "block";
// Show or hide clear button
clearBtn.style.display =
(searchInput.value.trim() !== "" || alphabetSelect.value !== "")
? "inline-block"
: "none";
// Rebuild dropdown based on visible buttons
populateAlphabetDropdown(true);
}
/* === CLEAR BUTTON HANDLER === */
clearBtn.addEventListener("click", function () {
searchInput.value = "";
alphabetSelect.value = "";
clearBtn.style.display = "none";
// Reset all buttons visible
buttons.forEach(btn => btn.style.display = "inline-block");
// Rebuild dropdown from all buttons
populateAlphabetDropdown(false);
noResultsMsg.style.display = "none";
});
/* === EVENT LISTENERS === */
alphabetSelect.addEventListener("change", updateButtonVisibility);
searchInput.addEventListener("keyup", updateButtonVisibility);
// Initial render
updateButtonVisibility();
});
// ////////////////////////

28
src/main/resources/static/assets/js/login-script.js

@ -0,0 +1,28 @@
function handleLoginSubmit2(event) {
event.preventDefault(); // prevent normal form submission
const form = document.getElementById("frm-usr-login-det");
const formData = new FormData(form);
fetch(contextPath + 'api/val-app-usr-login', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.result) {
let usrName = data.usrName && data.usrName.length > 0 ? data.usrName : null;
if (usrName) {
alert("Login Success");
window.location.href = contextPath + 'redirect';
} else {
alert("No role found for this user!");
}
} else {
alert(data.error || "Login failed");
}
})
.catch(err => {
console.error("Error during login:", err);
});
}

136
src/main/resources/static/assets/js/update-url-data-script.js

@ -0,0 +1,136 @@
// Global flags
let otpSent = false;
let isAdmin = false;
async function loadRole() {
try {
const res = await fetch(`${contextPath}api/get-role`, {
method: "POST"
});
const data = await res.json();
if (!data.authenticated) {
isAdmin = false;
return;
}
isAdmin = data.isAdmin;
} catch (e) {
// swallow network errors
console.error("Error: ", error);
}
}
// Open the login modal
function openLoginModal() {
if (isAdmin) {
// For admin → directly go to the post-verification page
window.open(`${contextPath}view-login-det`, '_blank');
return;
}
const modal = document.getElementById("login-modal");
modal.classList.remove("hide");
modal.classList.add("show");
modal.style.display = "flex"; // make it visible immediately before animation
}
// Load role when page loads
document.addEventListener("DOMContentLoaded", () => {
loadRole();
});
document.addEventListener("DOMContentLoaded", function() {
const dropdown = document.getElementById("urlDropdown");
const urlStatDropdown = document.getElementById("urlStatDropdown");
const urlLinkInput = document.getElementById("urlDataLink");
const urlNameInput = document.getElementById("urlDataName");
const urlUsrNameInput = document.getElementById("urlDataUsrName");
const urlUsrPassInput = document.getElementById("urlDataUsrPass");
const urlCatNameInput = document.getElementById("urlCatName");
const togglePass = document.getElementById("togglePass");
// safety check
if (!dropdown || !togglePass) {
return;
}
// if (!dropdown) return;
// --- Dropdown change handler ---
dropdown.addEventListener("change", function() {
const selected = dropdown.options[dropdown.selectedIndex];
if (!selected.value) {
urlLinkInput.value = "";
urlNameInput.value = "";
urlUsrNameInput.value = "";
urlUsrPassInput.value = "";
urlCatNameInput.value = "";
return;
}
const statId = selected.getAttribute("data-statId");
if (statId) {
urlStatDropdown.value = statId;
}
const link = selected.getAttribute("data-link");
const name = selected.getAttribute("data-name");
const usrname = selected.getAttribute("data-usrname");
const pass = selected.getAttribute("data-pass");
const urlCatName = selected.getAttribute("data-urlCatName");
urlLinkInput.value = link || "";
urlNameInput.value = name || "";
urlUsrNameInput.value = (usrname && usrname !== "-") ? usrname : "";
urlUsrPassInput.value = (pass && pass !== "-") ? pass : "";
urlCatNameInput.value = (urlCatName && urlCatName !== "-") ? urlCatName : "";
// Reset to hidden state
urlUsrPassInput.type = "password";
togglePass.classList.add("fa-eye");
togglePass.classList.remove("fa-eye-slash");
});
// --- Toggle Password Visibility ---
togglePass.addEventListener("click", function() {
const isPassword = urlUsrPassInput.type === "password";
urlUsrPassInput.type = isPassword ? "text" : "password";
togglePass.classList.toggle("fa-eye");
togglePass.classList.toggle("fa-eye-slash");
});
const form = document.getElementById("frm-url-det");
// Existing form submission logic...
form.addEventListener("submit", function(e) {
e.preventDefault(); // Prevent default form submission
const formData = new FormData(form);
fetch(form.getAttribute("action"), {
method: "POST",
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok: " + response.statusText);
}
return response.json();
})
.then(data => {
if (data.resStat) {
alert("Success: " + data.res);
form.reset();
} else {
alert("Failed: " + data.res);
}
})
.catch(error => {
console.error("Error updating url:", error);
alert("Error updating url. Please try again.");
});
});
});

56
src/main/resources/static/assets/js/view-login-details-script.js

@ -0,0 +1,56 @@
document.addEventListener("DOMContentLoaded", function() {
// Auto-close window after 2 minutes (120000 ms)
setTimeout(() => {
window.close();
}, 120000);
// Initialize DataTables
const table = $("#url-det-tbl").DataTable({
pageLength: -1, // default = show ALL rows
lengthMenu: [
[5, 10, 20, 50, -1], // values
[5, 10, 20, 50, "All"] // labels
],
ordering: false, // Disable client-side sorting
language: {
info: "Showing _START_ to _END_ of _TOTAL_ entries",
lengthMenu: "Show _MENU_ entries"
},
dom: 'lfrtip', // hides default search bar
// Auto-insert S.No
drawCallback: function(settings) {
let api = this.api();
api.rows({ page: 'current' }).every(function(rowIdx, tableLoop, rowLoop) {
let index = rowIdx + 1;
api.cell(rowIdx, 0).data(index);
});
}
});
// Move DataTables elements to your custom divs
$('#custom-search').html($('.dataTables_filter'));
$('#custom-pagination').html($('.dataTables_paginate'));
$('#custom-info').html($('.dataTables_info'));
$('#custom-length').html($('.dataTables_length'));
});
const toggleIcons = document.querySelectorAll(".toggle-pass");
toggleIcons.forEach(icon => {
icon.addEventListener("click", function() {
const targetId = this.getAttribute("data-target");
const passwordElement = document.getElementById(targetId);
const originalPassword = this.getAttribute("data-pass");
if (passwordElement.textContent.includes("*")) {
// Show actual password
passwordElement.textContent = originalPassword;
this.classList.replace("fa-eye", "fa-eye-slash");
} else {
// Hide password
passwordElement.textContent = "*".repeat(originalPassword.length);
this.classList.replace("fa-eye-slash", "fa-eye");
}
});
});

1123
src/main/resources/static/assets/popper-2.11.8/js/popper.min.js
File diff suppressed because it is too large
View File

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-100.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-200.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-300.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-500.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-600.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-regular.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-100.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-200.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-300.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-500.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-600.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-700.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-regular.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-100.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-200.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-300.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-500.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-600.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-700.woff2

BIN
src/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-regular.woff2

21
src/main/resources/templates/fragments/footer.html

@ -0,0 +1,21 @@
<th:block th:fragment="footer">
<!-- jQuery library JS -->
<script th:src="@{/assets/jquery-3.6.4/js/jquery-3.6.4.min.js}" type="text/javascript"></script>
<!-- DataTables JS -->
<script th:src="@{/assets/datatables-1.13.6/js/jquery.dataTables.min.js}" type="text/javascript"></script>
<script th:src="@{/assets/datatables-1.13.6/js/dataTables.bootstrap5.min.js}" type="text/javascript"></script>
<!-- DataTables Buttons JS -->
<script th:src="@{/assets/datatables-buttons-2.4.1/js/dataTables.buttons.min.js}" type="text/javascript"></script>
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.bootstrap5.min.js}" type="text/javascript"></script>
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js}" type="text/javascript"></script>
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.print.min.js}" type="text/javascript"></script>
<!-- Bootstrap JS -->
<script th:src="@{/assets/popper-2.11.8/js/popper.min.js}" type="text/javascript"></script>
<script th:src="@{/assets/bootstrap-5.3.2/js/bootstrap.min.js}" type="text/javascript"></script>
<!-- CSRF sscript-->
<script th:src="@{/assets/js/fetch-csrf-script.js}" type="text/javascript" defer></script>
<!-- Custom JS -->
<script th:src="@{/assets/js/go-back-script.js}" type="text/javascript"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</th:block>

22
src/main/resources/templates/fragments/header.html

@ -0,0 +1,22 @@
<th:block th:fragment="header">
<!-- Awesome Font -->
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" th:href="@{/assets/bootstrap-5.3.2/css/bootstrap.min.css}" type="text/css">
<!-- CSS for Header -->
<link rel="stylesheet" th:href="@{/assets/css/header-styles.css}" type="text/css">
<div class="header">
<div class="logo-left">
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo">
<div class="logo-text">
<h2>GREATER</h2>
<h2>CHENNAI</h2>
<h2>CORPORATION</h2>
<p>பெருநகர சென்னை மாநகராட்சி</p>
</div>
</div>
<div class="title">
<h2>GCC IT Cell Outsourced Manpower Details</h2>
</div>
</div>
</th:block>

79
src/main/resources/templates/fragments/navbar.html

@ -0,0 +1,79 @@
<th:block th:fragment="navbar(curPage, isAdmin)">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<!-- Use container-fluid for full-width navbar -->
<!-- Toggle button -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Navigation items on the left -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto">
<!-- Superuser Navigation Items -->
<li class="nav-item"><a class="nav-link" th:href="@{/}" th:if="${curPage != 'home'}"> <i
class=" fa-solid fa-house"></i>
Home
</a></li>
<!-- Admin-only links, visible only if isAdmin is true -->
<th:block th:if="${isAdmin}">
<li class="nav-item">
<a class="nav-link" th:href="@{/?action=addEmployee}"
onclick="if (document.getElementById('addEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addEmployeeModal')).show(); return false; }">
<i class="fa-solid fa-circle-plus"></i> Add Employee
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/?action=updateEmployee}"
onclick="if (document.getElementById('updateEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('updateEmployeeModal')).show(); return false; }">
<i class="fa-solid fa-file-pen"></i> Update Employee
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/?action=deleteEmployee}"
onclick="if (document.getElementById('deleteEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('deleteEmployeeModal')).show(); return false; }">
<i class="fa-solid fa-trash-can"></i> Delete Employee
</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/?action=addVendorWorkOrder}"
onclick="if (document.getElementById('addVendorWorkOrderModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addVendorWorkOrderModal')).show(); return false; }">
<i class="fa-solid fa-folder-plus"></i> Add Vendor / Work Order
</a>
</li>
</th:block>
<!-- <li class="nav-item">
<a class="nav-link" href="#" onclick="openLoginModal(); return false;"
th:if="${curPage != 'view-login-det'}">
<i class="fa-solid fa-gauge"></i> Manpower Dashboard
</a>
</li> -->
</ul>
<!--Navigation item on the right-->
<!-- Admin-only links, visible only if isAdmin is true -->
<th:block th:if="${isAdmin}">
<ul class=" navbar-nav ms-auto" th:if="${curPage == 'home'}">
<li class=" nav-item">
<form th:action="@{/usr-logout}" method="post" class="m-0 p-0">
<input th:if="${_csrf != null}" type="hidden" th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
<button type="submit" class="dropdown-item">
<i class="fa-solid fa-right-from-bracket"></i> Logout
</button>
</form>
</li>
</ul>
</th:block>
<th:block th:unless="${isAdmin}">
<ul class="navbar-nav ms-auto" th:if="${curPage == 'home'}">
<li class="nav-item"><a class="nav-link" th:href="@{/login}" th:if="${curPage == 'home'}"> <i
class="fa-solid fa-right-to-bracket"></i>
Login
</a></li>
</ul>
</th:block>
</div>
</div>
</nav>
</th:block>

3456
src/main/resources/templates/home/home.html
File diff suppressed because it is too large
View File

88
src/main/resources/templates/home/login.html

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GCC IT Cell Manpower Details</title>
<!-- CSRF Tokens -->
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
<!-- Include CSS -->
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/login-form-styles.css}">
<!-- Awesome Font -->
<link th:href="@{/assets/font-awesome-6.5.1/css/all.min.css}" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation" class="small-logo">
<h3>GREATER CHENNAI CORPORATION</h3>
<h4>பெருநகர சென்னை மாநகராட்சி</h4>
<br>
<h3>GCC IT Cell Outsourced Manpower Details</h3>
<br>
<h2>Login</h2>
<!--<form method="post" th:action="@{/liveurls/api/val-app-usr-login}" id="frm-usr-login-det"
name="frm-usr-login-det">-->
<form id="frm-usr-login-det" name="frm-usr-login-det">
<div class="input-field">
<input type="text" name="usrName" id="usrName" placeholder="Enter Username">
<div class="underline"></div>
</div>
<div class="input-field">
<input type="password" name="usrPass" id="usrPass" placeholder="Enter Password">
<div class="underline"></div>
</div>
<input type="submit" value="Login" onclick="handleLoginSubmit(event)">
</form>
</div>
<!-- Footer Fragment -->
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<!-- Custom JS -->
<!--<script th:src="@{/assets/js/login-script.js}" type="text/javascript"></script>-->
<script th:inline="javascript">
const contextPath = /*[[@{/}]]*/ '';
</script>
<script>
function handleLoginSubmit(event) {
event.preventDefault();
const form = document.getElementById("frm-usr-login-det");
const formData = new FormData(form);
fetch(contextPath + 'api/val-app-usr-login', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.result) {
let usrName = data.usrName && data.usrName.length > 0 ? data.usrName : null;
if (usrName) {
console.log("Redirecting to:", contextPath + 'redirect');
alert("Login Success");
window.location.href = contextPath + 'redirect';
} else {
alert("No role found for this user!");
}
} else {
alert(data.error || "Login failed");
}
})
.catch(err => {
console.error("Error during login:", err);
});
}
</script>
</body>
</html>

941
src/main/resources/templates/home/view-login-details.html

@ -0,0 +1,941 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GCC IT Cell Outsourced Manpower Details</title>
<!-- CSRF Tokens -->
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" />
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" />
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/body-form-styles.css}">
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/home-styles.css}">
<th:block th:replace="~{fragments/header :: header}"></th:block>
<style>
.spreadsheet-container {
background: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 25px;
margin: 20px auto;
max-width: 100%;
}
.sheet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 2px solid #eef2f5;
padding-bottom: 15px;
}
.sheet-title {
color: #0f5c78;
font-weight: 800;
font-size: 1.8rem;
margin: 0;
display: flex;
align-items: center;
gap: 12px;
}
.sheet-date-badge {
background: #eef7fa;
color: #0f5c78;
font-weight: 700;
padding: 6px 14px;
border-radius: 20px;
font-size: 0.9rem;
border: 1px solid #d4e7ee;
}
.table-responsive {
overflow-x: auto;
border-radius: 8px;
border: 1px solid #dcdfe3;
}
.sheet-table {
width: 100%;
border-collapse: collapse;
font-size: 0.92rem;
text-align: left;
background: #fff;
}
.sheet-table th {
background-color: #0f5c78;
color: #ffffff;
font-weight: 700;
padding: 12px 10px;
border: 1px solid #0b4960;
text-align: center;
vertical-align: middle;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.5px;
}
.sheet-table td {
padding: 8px 10px;
border: 1px solid #e1e4e8;
vertical-align: middle;
color: #2c3e50;
}
.sheet-table tbody tr:nth-child(even) {
background-color: #f9fbfb;
}
.sheet-table tbody tr:hover {
background-color: #f1f6f8;
}
.col-center {
text-align: center;
}
.col-right {
text-align: right;
}
/* Inputs */
.sheet-input-number {
width: 75px;
padding: 5px 8px;
border: 1px solid #ccc;
border-radius: 4px;
text-align: center;
font-weight: 600;
color: #0f5c78;
transition: border-color 0.2s, box-shadow 0.2s;
}
.sheet-input-number:focus {
border-color: #1e82a6;
box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15);
outline: none;
}
.sheet-input-text {
width: 90%;
min-width: 150px;
padding: 5px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-weight: 500;
transition: border-color 0.2s, box-shadow 0.2s;
}
.sheet-input-text:focus {
border-color: #1e82a6;
box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15);
outline: none;
}
/* Totals Row */
.totals-row {
background-color: #e8eff2 !important;
font-weight: 800;
border-top: 2px solid #0f5c78;
}
.totals-row td {
color: #0f5c78 !important;
font-size: 0.98rem;
border-top: 2px solid #0f5c78;
}
/* Action Buttons */
.action-bar {
display: flex;
justify-content: flex-end;
margin-top: 20px;
gap: 15px;
}
.save-btn {
background-color: #10b981;
color: white;
border: none;
padding: 12px 28px;
font-size: 1rem;
font-weight: 700;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 6px rgba(16, 185, 129, 0.2);
transition: background-color 0.2s, transform 0.1s;
}
.save-btn:hover {
background-color: #059669;
}
.save-btn:active {
transform: scale(0.98);
}
/* Remarks styling */
.vacant-highlight {
color: #e74c3c;
font-weight: 700;
}
.no-vacant {
color: #27ae60;
font-weight: 700;
}
/* Custom Dashboard Tabs */
.dashboard-tabs {
display: flex;
gap: 15px;
margin-bottom: 25px;
border-bottom: 2px solid #eef2f5;
padding-bottom: 10px;
}
.dashboard-tab {
padding: 10px 22px;
font-size: 1.05rem;
font-weight: 700;
color: #7f8c8d;
background: none;
border: none;
border-radius: 8px 8px 0 0;
cursor: pointer;
position: relative;
transition: all 0.3s ease;
}
.dashboard-tab:hover {
color: #0f5c78;
background-color: rgba(15, 92, 120, 0.05);
}
.dashboard-tab.active {
color: #0f5c78;
}
.dashboard-tab.active::after {
content: '';
position: absolute;
bottom: -12px;
left: 0;
width: 100%;
height: 4px;
background-color: #0f5c78;
border-radius: 2px;
}
/* Vendor Directory Styles */
.vendor-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.vendor-card {
background: #ffffff;
border: 1px solid #eef2f6;
border-radius: 12px;
padding: 22px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
}
.vendor-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background-color: #0f5c78;
opacity: 0.8;
}
.vendor-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(15, 92, 120, 0.08);
border-color: #d4e7ee;
}
.vendor-card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
}
.vendor-card-title {
font-size: 1.15rem;
font-weight: 800;
color: #2c3e50;
margin: 0;
text-transform: capitalize;
line-height: 1.3;
}
.vendor-card-desc {
font-size: 0.9rem;
color: #7f8c8d;
line-height: 1.4;
margin-bottom: 20px;
flex-grow: 1;
}
.vendor-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 15px;
border-top: 1px dashed #eef2f6;
}
.vendor-badge {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 12px;
background-color: #eef7fa;
color: #0f5c78;
}
/* Employee Count Interactive Badge */
.emp-count-badge {
background: linear-gradient(135deg, #0f5c78, #1e82a6);
color: #ffffff;
font-weight: 800;
font-size: 1.05rem;
padding: 8px 18px;
border-radius: 30px;
cursor: pointer;
border: none;
box-shadow: 0 4px 10px rgba(30, 130, 166, 0.25);
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.emp-count-badge:hover {
transform: scale(1.08);
box-shadow: 0 6px 15px rgba(30, 130, 166, 0.4);
background: linear-gradient(135deg, #1e82a6, #249cc8);
color: #ffffff;
}
.emp-count-badge:active {
transform: scale(0.96);
}
/* Modal styling overrides */
.modal-custom-header {
background-color: #0f5c78;
color: white;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-custom-title {
font-weight: 800;
font-size: 1.35rem;
display: flex;
align-items: center;
gap: 10px;
margin: 0;
}
.modal-search-box {
position: relative;
margin-bottom: 20px;
flex-grow: 1;
}
.modal-search-box input {
padding: 10px 15px 10px 40px;
border: 1px solid #dcdfe3;
border-radius: 8px;
width: 100%;
font-size: 0.95rem;
transition: all 0.3s ease;
}
.modal-search-box input:focus {
border-color: #0f5c78;
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15);
outline: none;
}
.modal-search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #7f8c8d;
}
.modal-table {
font-size: 0.9rem;
}
.modal-table th {
background-color: #eef7fa !important;
color: #0f5c78 !important;
font-weight: 700;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
border-bottom: 2px solid #0f5c78 !important;
}
.modal-table td {
vertical-align: middle;
}
.modal-body {
padding: 25px;
}
.export-btn {
background-color: #27ae60;
color: white;
border: none;
padding: 8px 16px;
font-size: 0.9rem;
font-weight: 700;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.export-btn:hover {
background-color: #219653;
color: white;
}
</style>
</head>
<body>
<th:block th:replace="~{fragments/navbar :: navbar(curPage=${curPage}, isAdmin=${isAdmin})}"></th:block>
<div class="container" style="max-width: 95%;">
<div class="spreadsheet-container">
<!-- Dashboard Tabs -->
<div class="dashboard-tabs">
<button class="dashboard-tab active" id="btn-vendor-tab" onclick="switchTab('vendor-tab')">
<i class="fa-solid fa-users-gear"></i> Vendor Directory & Counts
</button>
<button class="dashboard-tab" id="btn-manpower-tab" onclick="switchTab('manpower-tab')">
<i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics
</button>
</div>
<!-- Tab 1: Vendor Directory & Counts -->
<div id="vendor-tab-content" class="tab-content">
<div class="sheet-header">
<h2 class="sheet-title">
<i class="fa-solid fa-cubes text-primary"></i> Vendor Directory & Employees
</h2>
<span class="sheet-date-badge">Overall Count Wise Summary</span>
</div>
<div class="vendor-grid">
<div class="vendor-card" th:each="vendor : ${vendorDetails}">
<div class="vendor-card-header">
<h3 class="vendor-card-title" th:text="${vendor.vendor_name}">Vendor Name</h3>
<span class="vendor-badge" th:text="'Order: ' + ${vendor.orderby}">Order</span>
</div>
<p class="vendor-card-desc"
th:text="${vendor.description != null ? vendor.description : 'No description provided.'}">
Description</p>
<div class="vendor-card-footer">
<span style="font-weight: 600; color: #7f8c8d; font-size: 0.9rem;">
Employees Count:
</span>
<button class="emp-count-badge"
th:attr="onclick=|fetchAndShowEmployees('${vendor.vendor_name}')|">
<i class="fa-solid fa-users"></i>
<span th:text="${vendor.actual_employee_count}">0</span>
</button>
</div>
</div>
</div>
</div>
<!-- Tab 2: Manpower Statistics (Existing Spreadsheet) -->
<div id="manpower-tab-content" class="tab-content" style="display: none;">
<div class="sheet-header">
<h2 class="sheet-title">
<i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics Report
</h2>
<span class="sheet-date-badge" id="current-date-badge">As on 21.05.2026</span>
</div>
<div class="table-responsive">
<table class="sheet-table" id="manpower-sheet-table">
<thead>
<tr>
<th style="width: 50px;">S.No</th>
<th>Description (Designation)</th>
<th>Vendor Name</th>
<th style="width: 120px;">Manpower Count<br>as per Tender</th>
<th style="width: 120px;">Available<br>Manpower Count</th>
<th>Working Location</th>
<th style="width: 110px;">Registered<br>in App</th>
<th>Vacant / Remarks</th>
</tr>
</thead>
<tbody>
<tr th:each="entry, stat : ${manpowerEntries}">
<td class="col-center" th:text="${stat.count}">1</td>
<td th:text="${entry.description}" style="font-weight: 600;">Description</td>
<td th:text="${entry.vendor_name}">Vendor Name</td>
<!-- Read-only Tender Count -->
<td class="col-center tender-cell" th:text="${entry.tender_count}"
style="font-weight: 700;">0</td>
<!-- Editable Available Count -->
<td class="col-center">
<input type="number" min="0" class="sheet-input-number available-input"
th:value="${entry.available_count}" th:data-id="${entry.id}"
oninput="recalculateTotals()">
</td>
<td th:text="${entry.working_location}">Location</td>
<td class="col-center" th:text="${entry.registered_count}">0</td>
<!-- Editable Remarks -->
<td>
<input type="text" class="sheet-input-text remarks-input"
th:value="${entry.vacant_remarks}" th:data-id="${entry.id}"
oninput="recalculateTotals()">
</td>
</tr>
</tbody>
<tfoot>
<tr class="totals-row">
<td colspan="3" style="text-align: right; padding-right: 20px;">Total No</td>
<td class="col-center" id="total-tender">319</td>
<td class="col-center" id="total-available">292</td>
<td></td>
<td></td>
<td id="total-vacant" style="font-weight: 800; padding-left: 20px;">30</td>
</tr>
</tfoot>
</table>
</div>
<div class="action-bar">
<button class="save-btn" onclick="saveManpowerData()">
<i class="fa-solid fa-floppy-disk"></i> Save Changes
</button>
</div>
</div>
</div>
</div>
<!-- Employee Details Modal -->
<div class="modal fade" id="employeeDetailsModal" tabindex="-1" aria-labelledby="employeeDetailsModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content"
style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);">
<div class="modal-custom-header">
<h5 class="modal-custom-title" id="employeeDetailsModalLabel">
<i class="fa-solid fa-users-viewfinder"></i> Employee Details - <span id="modal-vendor-name"
style="text-decoration: underline;">Vendor</span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Search and Export Toolbar -->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-3 mb-4">
<div class="modal-search-box flex-grow-1 m-0">
<i class="fa-solid fa-magnifying-glass"></i>
<input type="text" id="employee-search-input"
placeholder="Search employees by name, designation, location, code..."
oninput="filterEmployees()">
</div>
<div class="d-flex align-items-center gap-2 w-100 w-md-auto justify-content-md-end">
<select id="modal-designation-filter" class="form-select form-select-sm"
style="height: 44px; border-radius: 8px; border: 1px solid #cbd5e1; padding: 0 35px 0 15px; font-weight: 600; min-width: 200px;"
onchange="filterEmployees()">
<option value="">- All Designations -</option>
</select>
<button class="export-btn text-nowrap" onclick="exportEmployeesToCSV()">
<i class="fa-solid fa-file-csv"></i> Export to CSV
</button>
</div>
</div>
<!-- Table of Employees -->
<div class="table-responsive" style="max-height: 450px; border: 1px solid #eef2f6;">
<table class="table table-hover modal-table" id="modal-employee-table">
<thead>
<tr>
<th style="width: 60px;" class="text-center">S.No</th>
<th>Emp Code</th>
<th>Name</th>
<th>Email</th>
<th>Mobile</th>
<th>User Type</th>
<th>Designation</th>
<th>Location</th>
<th>Vendor Name</th>
</tr>
</thead>
<tbody id="modal-employee-tbody">
<!-- Dynamically populated -->
</tbody>
</table>
<div id="no-employees-found" class="text-center text-muted py-5" style="display: none;">
<i class="fa-solid fa-user-slash fs-1 mb-3 text-secondary"></i>
<p class="fw-bold mb-0">No matching employees found for this vendor.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<th:block th:replace="~{fragments/footer :: footer}"></th:block>
<!-- Inline Spreadsheet Script -->
<script th:inline="javascript">
const contextPath = /*[[@{/}]]*/ '';
const vendorDetailsData = /*[[${vendorDetails}]]*/[];
const allDesignations = /*[[${designations}]]*/[];
// Globals to store current modal state
let currentVendorName = "";
let currentVendorEmployees = [];
let filteredVendorEmployees = [];
// Handle switching between dynamic dashboard and spreadsheet
function switchTab(tabId) {
const tabs = document.querySelectorAll(".dashboard-tab");
const tabContents = document.querySelectorAll(".tab-content");
tabs.forEach(t => t.classList.remove("active"));
tabContents.forEach(c => c.style.display = "none");
if (tabId === 'vendor-tab') {
document.getElementById("btn-vendor-tab").classList.add("active");
document.getElementById("vendor-tab-content").style.display = "block";
} else if (tabId === 'manpower-tab') {
document.getElementById("btn-manpower-tab").classList.add("active");
document.getElementById("manpower-tab-content").style.display = "block";
}
}
// Fetch employee details from API dynamically and render in Modal
function fetchAndShowEmployees(vendorName, designationFilter = null) {
currentVendorName = vendorName;
let displayTitle = vendorName;
document.getElementById("modal-vendor-name").textContent = displayTitle;
// Populate modal designation dropdown based on selected vendor ID
const modalDesigFilter = document.getElementById("modal-designation-filter");
modalDesigFilter.innerHTML = '<option value="">- All Designations -</option>';
const vendor = vendorDetailsData.find(v => v.vendor_name === vendorName);
if (vendor && vendor.designationDetails) {
vendor.designationDetails.forEach(des => {
const opt = document.createElement("option");
opt.value = des.designation;
let displayText = des.designation;
const fileNo = des.File_no || des.file_no;
if (fileNo && fileNo !== '-' && fileNo !== '') {
displayText += " - " + fileNo;
}
opt.textContent = displayText;
modalDesigFilter.appendChild(opt);
});
}
if (designationFilter) {
modalDesigFilter.value = designationFilter;
} else {
modalDesigFilter.value = "";
}
// Show Loading state inside tbody
const tbody = document.getElementById("modal-employee-tbody");
tbody.innerHTML = `
<tr>
<td colspan="9" class="text-center py-5">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-secondary fw-semibold">Fetching employee records from database...</p>
</td>
</tr>
`;
document.getElementById("no-employees-found").style.display = "none";
document.getElementById("employee-search-input").value = "";
// Show Modal using Bootstrap
const modalElement = document.getElementById("employeeDetailsModal");
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
modal.show();
// Fetch from REST API
fetch(contextPath + 'api/vendor-employees?vendorName=' + encodeURIComponent(vendorName))
.then(res => res.json())
.then(data => {
currentVendorEmployees = data;
filterEmployees();
})
.catch(err => {
console.error("Error fetching employees:", err);
tbody.innerHTML = `
<tr>
<td colspan="9" class="text-center py-5 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i>
<p>Failed to connect to server and fetch database records. Please try again.</p>
</td>
</tr>
`;
});
}
// Render the employee list inside the modal table
function renderEmployeeTable(employees) {
const tbody = document.getElementById("modal-employee-tbody");
const noEmployeesEl = document.getElementById("no-employees-found");
tbody.innerHTML = "";
if (employees.length === 0) {
noEmployeesEl.style.display = "block";
return;
}
noEmployeesEl.style.display = "none";
employees.forEach((emp, index) => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td class="text-center fw-bold text-secondary">${index + 1}</td>
<td class="fw-semibold text-primary">${escapeHtml(emp.emp_code || '-')}</td>
<td class="fw-bold text-dark">${escapeHtml(emp.name || '-')}</td>
<td>${escapeHtml(emp.email || '-')}</td>
<td>${escapeHtml(emp.mobile || '-')}</td>
<td><span class="badge bg-secondary">${escapeHtml(emp.user_type || '-')}</span></td>
<td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td>
<td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td>
<td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td>
`;
tbody.appendChild(tr);
});
}
// Instant fuzzy client-side search inside the modal
function filterEmployees() {
const query = document.getElementById("employee-search-input").value.toLowerCase().trim();
const selectedDesignation = document.getElementById("modal-designation-filter").value.toLowerCase().trim();
let list = currentVendorEmployees;
// Apply designation filter
if (selectedDesignation !== "") {
list = list.filter(emp => emp.designation && emp.designation.trim().toLowerCase() === selectedDesignation);
}
if (query === "") {
filteredVendorEmployees = list;
} else {
filteredVendorEmployees = list.filter(emp => {
return (emp.name && emp.name.toLowerCase().includes(query)) ||
(emp.emp_code && emp.emp_code.toLowerCase().includes(query)) ||
(emp.designation && emp.designation.toLowerCase().includes(query)) ||
(emp.location && emp.location.toLowerCase().includes(query)) ||
(emp.email && emp.email.toLowerCase().includes(query)) ||
(emp.mobile && emp.mobile.toLowerCase().includes(query)) ||
(emp.vendor_name && emp.vendor_name.toLowerCase().includes(query)) ||
(emp.user_type && emp.user_type.toLowerCase().includes(query));
});
}
renderEmployeeTable(filteredVendorEmployees);
}
// Premium CSV Exporter
function exportEmployeesToCSV() {
if (filteredVendorEmployees.length === 0) {
alert("No employee records to export!");
return;
}
let csvContent = "data:text/csv;charset=utf-8,";
csvContent += "S.No,Employee Code,Name,Email,Mobile,User Type,Designation,Location,Vendor Name\n";
filteredVendorEmployees.forEach((emp, index) => {
const row = [
index + 1,
`"${(emp.emp_code || '-').replace(/"/g, '""')}"`,
`"${(emp.name || '-').replace(/"/g, '""')}"`,
`"${(emp.email || '-').replace(/"/g, '""')}"`,
`"${(emp.mobile || '-').replace(/"/g, '""')}"`,
`"${(emp.user_type || '-').replace(/"/g, '""')}"`,
`"${(emp.designation || '-').replace(/"/g, '""')}"`,
`"${(emp.location || '-').replace(/"/g, '""')}"`,
`"${(emp.vendor_name || '-').replace(/"/g, '""')}"`
];
csvContent += row.join(",") + "\n";
});
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
// Filename format: [vendor_name]_employees.csv
const sanitizedVendorName = currentVendorName.toLowerCase().replace(/[^a-z0-9]/g, "_");
link.setAttribute("download", `${sanitizedVendorName}_employees.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Helper function to safely escape HTML to prevent XSS
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// Dynamic calculation when page loads
document.addEventListener("DOMContentLoaded", () => {
// Update the date badge dynamically to current date format
const d = new Date();
const day = String(d.getDate()).padStart(2, '0');
const month = String(d.getMonth() + 1).padStart(2, '0');
const year = d.getFullYear();
document.getElementById("current-date-badge").textContent = `As on ${day}.${month}.${year}`;
recalculateTotals();
});
// Recalculates Tender, Available, and Vacant counts dynamically in the footer
function recalculateTotals() {
const table = document.getElementById("manpower-sheet-table");
const rows = table.querySelectorAll("tbody tr");
let tenderSum = 0;
let availableSum = 0;
let vacantSum = 0;
rows.forEach(row => {
// Tender sum
const tenderVal = parseInt(row.querySelector(".tender-cell").textContent) || 0;
tenderSum += tenderVal;
// Available sum
const availableInput = row.querySelector(".available-input");
const availableVal = parseInt(availableInput.value) || 0;
availableSum += availableVal;
// Vacant Sum parsing: Extract leading digit or number from vacant input
const remarksInput = row.querySelector(".remarks-input");
const remarksVal = remarksInput.value.trim();
// Extract the first number found in the remarks input
const numMatch = remarksVal.match(/^-?\d+/);
if (numMatch) {
vacantSum += parseInt(numMatch[0]);
}
});
document.getElementById("total-tender").textContent = tenderSum;
document.getElementById("total-available").textContent = availableSum;
document.getElementById("total-vacant").textContent = vacantSum;
}
// Save dynamic spreadsheet updates back to database
function saveManpowerData() {
const table = document.getElementById("manpower-sheet-table");
const rows = table.querySelectorAll("tbody tr");
const payload = [];
rows.forEach(row => {
const availableInput = row.querySelector(".available-input");
const remarksInput = row.querySelector(".remarks-input");
payload.push({
id: parseInt(availableInput.dataset.id),
availableCount: parseInt(availableInput.value) || 0,
vacantRemarks: remarksInput.value
});
});
// CSRF tokens
const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content");
const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
const headers = {
'Content-Type': 'application/json'
};
if (csrfToken && csrfHeader) {
headers[csrfHeader] = csrfToken;
}
// Save to API
fetch(contextPath + 'api/save-manpower-stats', {
method: 'POST',
headers: headers,
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if (data.success) {
alert("Changes saved successfully to MySQL database!");
window.location.reload();
} else {
alert("Failed to save changes: " + (data.error || "Unknown error"));
}
})
.catch(err => {
console.error("Error saving data:", err);
alert("Something went wrong. Failed to communicate with server.");
});
}
</script>
</body>
</html>

30
src/main/resources/templates/modals/login-modal.html

@ -0,0 +1,30 @@
<div th:fragment="login-modal">
<!-- Overlay -->
<div id="login-modal" class="login-modal-overlay">
<!-- Modal Box -->
<div class="login-modal-box">
<span class="close-btn" onclick="closeLoginModal()">&times;</span>
<h3 class="login-title">Login</h3>
<form id="otp-form" class="login-form">
<label for="mobNum" class="form-label">Mobile Number <span class="req">*</span></label>
<select id="mobNum" name="mobNum" class="form-input" required>
<option value="">-- Select Mobile Number --</option>
</select>
<!--<input type="tel" id="mobNum" name="mobNum" placeholder="Enter your mobile number" required
class="form-input">-->
<!-- Hidden OTP Section (initially hidden) -->
<div id="otp-section" style="display: none; margin-top: 15px;">
<label for="otp" class="form-label">Enter OTP <span class="req">*</span></label>
<input type="number" id="otp" name="otp" placeholder="Enter received OTP" class="form-input"
maxlength="5">
</div>
<!-- Button Row -->
<div class="btn-row" id="button-section">
<button type="submit" id="send-otp-btn" class="login-btn-send">Get OTP</button>
</div>
</form>
<!-- Message -->
<p id="otp-message" style="text-align:center; margin-top:10px; color:#333;"></p>
</div>
</div>
</div>
Loading…
Cancel
Save