Added printing + Resume name input
Some checks failed
linter / quality (push) Failing after 6m25s
tests / ci (push) Failing after 12m38s

This commit is contained in:
2025-08-27 14:54:51 +02:00
parent 55a52086c1
commit f3ff6fd6ac
11 changed files with 303 additions and 16 deletions

View File

@@ -72,7 +72,7 @@ class ResumeController extends Controller
*/
public function update(UpdateResumeRequest $request, Resume $resume)
{
//
$resume->update($request->validated());
}
/**

View File

@@ -31,6 +31,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"concurrently": "^9.0.1",
"jspdf": "^3.0.1",
"laravel-vite-plugin": "^2.0.0",
"lucide-vue-next": "^0.468.0",
"reka-ui": "^2.2.0",

167
pnpm-lock.yaml generated
View File

@@ -29,6 +29,9 @@ importers:
concurrently:
specifier: ^9.0.1
version: 9.2.0
jspdf:
specifier: ^3.0.1
version: 3.0.1
laravel-vite-plugin:
specifier: ^2.0.0
version: 2.0.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1))
@@ -241,6 +244,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.28.3':
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -759,6 +766,12 @@ packages:
'@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/raf@3.4.3':
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
@@ -962,12 +975,21 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'}
hasBin: true
axios@1.11.0:
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
birpc@2.5.0:
resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
@@ -989,6 +1011,11 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
btoa@1.2.1:
resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
engines: {node: '>= 0.4.0'}
hasBin: true
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -1011,6 +1038,10 @@ packages:
caniuse-lite@1.0.30001735:
resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==}
canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -1059,10 +1090,16 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
core-js@3.45.1:
resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-line-break@2.1.0:
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -1109,6 +1146,9 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1256,6 +1296,9 @@ packages:
picomatch:
optional: true
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
figures@6.1.0:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
@@ -1369,6 +1412,10 @@ packages:
hookable@5.5.3:
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
html2canvas@1.4.1:
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
engines: {node: '>=8.0.0'}
human-signals@8.0.1:
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
engines: {node: '>=18.18.0'}
@@ -1468,6 +1515,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
jspdf@3.0.1:
resolution: {integrity: sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -1698,6 +1748,9 @@ packages:
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -1819,6 +1872,12 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
raf@3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
reka-ui@2.4.1:
resolution: {integrity: sha512-NB7DrCsODN8MH02BWtgiExygfFcuuZ5/PTn6fMgjppmFHqePvNhmSn1LEuF35nel6PFbA4v+gdj0IoGN1yZ+vw==}
peerDependencies:
@@ -1839,6 +1898,10 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
rollup@4.46.2:
resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -1914,6 +1977,10 @@ packages:
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
engines: {node: '>=0.10.0'}
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -1942,6 +2009,10 @@ packages:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
svg-pathdata@6.0.3:
resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==}
engines: {node: '>=12.0.0'}
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
@@ -1961,6 +2032,9 @@ packages:
engines: {node: '>=10'}
hasBin: true
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
tinyglobby@0.2.14:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
@@ -2032,6 +2106,9 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utrie@1.0.2:
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
vite-dev-rpc@1.1.0:
resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==}
peerDependencies:
@@ -2372,6 +2449,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@babel/runtime@7.28.3': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -2778,6 +2857,12 @@ snapshots:
'@types/qs@6.14.0': {}
'@types/raf@3.4.3':
optional: true
'@types/trusted-types@2.0.7':
optional: true
'@types/web-bluetooth@0.0.21': {}
'@typescript-eslint/eslint-plugin@8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)':
@@ -3079,6 +3164,8 @@ snapshots:
asynckit@0.4.0: {}
atob@2.1.2: {}
axios@1.11.0:
dependencies:
follow-redirects: 1.15.11
@@ -3089,6 +3176,9 @@ snapshots:
balanced-match@1.0.2: {}
base64-arraybuffer@1.0.2:
optional: true
birpc@2.5.0: {}
boolbase@1.0.0: {}
@@ -3113,6 +3203,8 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.2)
btoa@1.2.1: {}
buffer-from@1.1.2:
optional: true
@@ -3134,6 +3226,18 @@ snapshots:
caniuse-lite@1.0.30001735: {}
canvg@3.0.11:
dependencies:
'@babel/runtime': 7.28.3
'@types/raf': 3.4.3
core-js: 3.45.1
raf: 3.4.1
regenerator-runtime: 0.13.11
rgbcolor: 1.0.1
stackblur-canvas: 2.7.0
svg-pathdata: 6.0.3
optional: true
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -3184,12 +3288,20 @@ snapshots:
dependencies:
is-what: 4.1.16
core-js@3.45.1:
optional: true
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
css-line-break@2.1.0:
dependencies:
utrie: 1.0.2
optional: true
cssesc@3.0.0: {}
csstype@3.1.3: {}
@@ -3217,6 +3329,11 @@ snapshots:
detect-libc@2.0.4: {}
dompurify@3.2.6:
optionalDependencies:
'@types/trusted-types': 2.0.7
optional: true
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3423,6 +3540,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.3
fflate@0.8.2: {}
figures@6.1.0:
dependencies:
is-unicode-supported: 2.1.0
@@ -3525,6 +3644,12 @@ snapshots:
hookable@5.5.3: {}
html2canvas@1.4.1:
dependencies:
css-line-break: 2.1.0
text-segmentation: 1.0.3
optional: true
human-signals@8.0.1: {}
ignore@5.3.2: {}
@@ -3586,6 +3711,18 @@ snapshots:
json5@2.2.3: {}
jspdf@3.0.1:
dependencies:
'@babel/runtime': 7.28.3
atob: 2.1.2
btoa: 1.2.1
fflate: 0.8.2
optionalDependencies:
canvg: 3.0.11
core-js: 3.45.1
dompurify: 3.2.6
html2canvas: 1.4.1
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -3770,6 +3907,9 @@ snapshots:
perfect-debounce@1.0.0: {}
performance-now@2.1.0:
optional: true
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -3820,6 +3960,14 @@ snapshots:
queue-microtask@1.2.3: {}
raf@3.4.1:
dependencies:
performance-now: 2.1.0
optional: true
regenerator-runtime@0.13.11:
optional: true
reka-ui@2.4.1(typescript@5.9.2)(vue@3.5.18(typescript@5.9.2)):
dependencies:
'@floating-ui/dom': 1.7.3
@@ -3845,6 +3993,9 @@ snapshots:
rfdc@1.4.1: {}
rgbcolor@1.0.1:
optional: true
rollup@4.46.2:
dependencies:
'@types/estree': 1.0.8
@@ -3942,6 +4093,9 @@ snapshots:
speakingurl@14.0.1: {}
stackblur-canvas@2.7.0:
optional: true
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -3968,6 +4122,9 @@ snapshots:
dependencies:
has-flag: 4.0.0
svg-pathdata@6.0.3:
optional: true
tailwind-merge@3.3.1: {}
tailwindcss@4.1.12: {}
@@ -3991,6 +4148,11 @@ snapshots:
source-map-support: 0.5.21
optional: true
text-segmentation@1.0.3:
dependencies:
utrie: 1.0.2
optional: true
tinyglobby@0.2.14:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@@ -4052,6 +4214,11 @@ snapshots:
util-deprecate@1.0.2: {}
utrie@1.0.2:
dependencies:
base64-arraybuffer: 1.0.2
optional: true
vite-dev-rpc@1.1.0(vite@7.1.2(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)):
dependencies:
birpc: 2.5.0

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
import { Resume } from '@/types/resume';
import Button from '../ui/button/Button.vue';
import { exportToPdf } from '@/lib/pdfExport';
import { FileText } from 'lucide-vue-next';
const props = defineProps<{
resume: Resume,
}>();
function printResume() {
exportToPdf(document.getElementById('resume')!, (props.resume.name || 'Sans titre') + '.pdf');
}
</script>
<template>
<Button variant="outline" class="cursor-pointer" @click="printResume"><FileText />Exporter le CV</Button>
</template>

View File

@@ -16,5 +16,7 @@ const componentFile = defineAsyncComponent(
</script>
<template>
<div class="w-full">
<component :is="componentFile" :componentPlacement="props.componentPlacement" />
</div>
</template>

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
import { ref, Transition } from 'vue';
import { Save } from 'lucide-vue-next';
import Button from '../ui/button/Button.vue';
import Input from '../ui/input/Input.vue';
import { Form } from '@inertiajs/vue3';
import { httpApi } from '@/lib/utils';
import { Resume } from '@/types/resume';
const props = defineProps<{
resume: Resume,
resumeTitle: string,
}>();
const emit = defineEmits(['update:resume-title']);
const originalTitle = ref<string>(props.resumeTitle);
const titleChanged = ref<boolean>(false);
function saveTitle() {
const resume = { ...props.resume, name: props.resumeTitle };
resume['components_placements'] = null;
httpApi(route('resumes.update', props.resume), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'Accept': 'application/json'
},
body: JSON.stringify({ ...resume, _method: 'PUT' })
}).then(() => {
location.reload();
});
}
</script>
<template>
<div class="flex gap-1" >
<Input type="text" v-model="props.resumeTitle" placeholder="Sans titre" class="border p-2 rounded" @input="(event) => {emit('update:resume-title', event.target.value); titleChanged = (event.target.value !== originalTitle)}" />
<Transition>
<Button v-if="titleChanged" variant="outline" class="cursor-pointer transition" @click="saveTitle"><Save /></Button>
</Transition>
</div>
</template>
<style lang="css" scoped>
.v-enter-active,
.v-leave-active {
transition: opacity 0.3s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,19 +1,25 @@
<script setup lang="ts">
import { Resume, ResumeComponentPlacement } from '@/types/resume';
import ResumeComponent from './ResumeComponent.vue';
import PrintResumeButton from './PrintResumeButton.vue';
import ResumeNameInput from './ResumeNameInput.vue';
const props = defineProps<{
resume: Resume,
selectedComponent: ResumeComponentPlacement | null
}>();
const emit = defineEmits(['selected-component-change']);
const emit = defineEmits(['selected-component-change', 'update:resume-title']);
</script>
<template>
<div class="flex-2 w-full p-6">
<div class="flex-2 flex flex-col gap-3 w-full">
<div id="tools" class="w-full flex gap-3 justify-between">
<ResumeNameInput :resume="props.resume" :resumeTitle="props.resume.name" @update:resume-title="emit('update:resume-title', $event)" />
<PrintResumeButton :resume="props.resume" />
</div>
<div id="resume" class="aspect-[0.707317073] w-full max-w-[84.1cm] bg-white text-black">
<ResumeComponent v-for="componentPlacement in resume.components_placements" :key="componentPlacement.id" :componentPlacement="componentPlacement" @click="emit('selected-component-change', componentPlacement)" />
<ResumeComponent v-for="componentPlacement in props.resume.components_placements" :key="componentPlacement.id" :componentPlacement="componentPlacement" @click="emit('selected-component-change', componentPlacement)" />
</div>
</div>
</template>

View File

@@ -8,8 +8,9 @@ const props = defineProps<{
</script>
<template>
<div class="w-full">
<div class="w-full" style="background-color: aqua; height: 10%;">
I'm an email component : {{ props.componentPlacement?.component_data?.input_data[0].value }}
</div>
<div style="width: 365px; height: 50px; background-color: red;"></div>
</template>

View File

@@ -0,0 +1,13 @@
import { jsPDF } from "jspdf";
export function exportToPdf(element: HTMLElement, name: string) {
const pdf = new jsPDF();
pdf.html(element, {
callback: function (doc) {
doc.save(name);
},
width: 210,
windowWidth: element.scrollWidth,
});
}

View File

@@ -2,20 +2,24 @@
import AppLayout from '@/layouts/AppLayout.vue';
import { type BreadcrumbItem } from '@/types';
import { Head } from '@inertiajs/vue3';
import { Resume, ResumeComponent, ResumeComponentPlacement } from '@/types/resume';
import { Resume, ResumeComponentPlacement } from '@/types/resume';
import ResumeEditPanel from '@/components/resume/ResumeEditPanel.vue';
import ResumePreviewPanel from '@/components/resume/ResumePreviewPanel.vue';
import { ref } from 'vue';
import { computed, ref, watch } from 'vue';
const props = defineProps<{
resume: Resume
}>();
const localResume = ref({ ...props.resume });
const resumeTitle = computed<string>(() => (localResume.value.name == '' ? 'Sans titre' : localResume.value.name) ?? 'Sans titre');
const selectedComponent = ref<ResumeComponentPlacement | null>(null);
const breadcrumbs: BreadcrumbItem[] = [
{
title: props.resume?.name ?? 'Sans titre',
title: resumeTitle.value,
href: '/resumes/edit',
},
];
@@ -23,22 +27,39 @@ const breadcrumbs: BreadcrumbItem[] = [
function changeSelectedComponent(newComponent: ResumeComponentPlacement) {
selectedComponent.value = newComponent;
// Update the resume
props.resume.components_placements! = props.resume.components_placements!.map(component =>
localResume.value.components_placements! = localResume.value.components_placements!.map(component =>
component.id === newComponent.id ? newComponent : component
);
}
console.log('Resume : ', props.resume);
function changeResumeTitle(newTitle: string) {
console.log('Changing resume title to ', newTitle);
localResume.value.name = newTitle;
}
console.debug('Resume : ', localResume.value);
</script>
<template>
<Head title="Dashboard" />
<Head :title="resumeTitle" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="flex h-full flex-1 gap-4 rounded-xl p-4 overflow-x-auto">
<ResumeEditPanel :resume="props.resume" :selected-component="selectedComponent" @selected-component-change="changeSelectedComponent" />
<ResumePreviewPanel :resume="props.resume" :selected-component="selectedComponent" @selected-component-change="changeSelectedComponent" />
<ResumeEditPanel :resume="localResume" :selected-component="selectedComponent" @selected-component-change="changeSelectedComponent" />
<ResumePreviewPanel :resume="localResume" :selected-component="selectedComponent" @selected-component-change="changeSelectedComponent" @update:resume-title="changeResumeTitle" />
</div>
</AppLayout>
</template>
<style lang="css" scoped>
.v-enter-active,
.v-leave-active {
transition: opacity 0.3s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>

View File

@@ -23,11 +23,11 @@
{{-- Inline style to set the HTML background color based on our theme in app.css --}}
<style>
html {
background-color: oklch(1 0 0);
background-color: #ffffff;
}
html.dark {
background-color: oklch(0.145 0 0);
background-color: #0a0a0a;
}
</style>